Skip to content

Name Mangling

Sharpy uses Pythonic snake_case naming conventions in source code, but generates C# code following .NET PascalCase conventions. This document specifies the complete name transformation algorithm.

Overview

Sharpy Convention C# Output Example
snake_case functions PascalCase get_user_name()GetUserName()
snake_case parameters camelCase user_name: strstring userName
camelCase identifiers Preserved httpClienthttpClient
PascalCase identifiers Preserved XMLParserXMLParser
_private fields _PascalCase _user_count_UserCount
__name fields __PascalCase __private_field__PrivateField
__dunder__ methods Special mapping __init__ → constructor
CAPS_SNAKE_CASE constants PascalCase MAX_SIZEMaxSize

Transformation Algorithm

The name mangling algorithm transforms identifiers as follows:

Step 1: Handle Leading Underscores

Leading underscores are preserved for access modifier semantics:

Pattern Preservation Purpose
_name Keep single _ Private member
__name Keep double __ Private (name-mangled in Python style if needed)
__name__ Special handling Dunder method

Step 2: Detect Name Form

After stripping leading underscores and trailing underscores, the remaining body is classified into one of these forms:

Form Pattern Example
SnakeCase All lowercase + digits + single underscores get_user_name
ScreamingSnakeCase All uppercase + digits + single underscores MAX_SIZE
PascalCase Starts uppercase, no underscores HttpClient, XMLParser
CamelCase Starts lowercase, has uppercase, no underscores httpClient, iPhone
SingleWordLower All lowercase, no underscores hello
SingleWordUpper All uppercase, no underscores HTTP
Dunder __name__ bookends (length > 4) __init__, __str__
Unrecognized Anything else (consecutive underscores, mixed case with underscores) foo__bar, Foo_bar

Step 3: Transform Based on Form

The transformation depends on the detected form and the target convention:

For PascalCase target (functions, methods, class fields):

Form Transformation Example
SnakeCase Split on _, capitalize each segment get_user_nameGetUserName
ScreamingSnakeCase Split on _, title-case each segment MAX_SIZEMaxSize
PascalCase Pass through HttpClientHttpClient
CamelCase Pass through httpClienthttpClient
SingleWordLower Capitalize helloHello
SingleWordUpper Pass through HTTPHTTP
Unrecognized Pass through (SPY0453 if consecutive __) foo__barfoo__bar

For camelCase target (parameters, local variables):

Form Transformation Example
SnakeCase Split on _, lowercase first, capitalize rest user_nameuserName
ScreamingSnakeCase Split on _, fully lower first, title-case rest MAX_SIZEmaxSize
PascalCase Lowercase first char HttpClienthttpClient
CamelCase Pass through httpClienthttpClient
SingleWordLower Pass through hellohello
SingleWordUpper Fully lowercase HTTPhttp
Unrecognized Pass through (SPY0453 if consecutive __) foo__barfoo__bar

For constant context (module-level constants):

Constants use the same rules as PascalCase target, except SingleWordUpper is normalized: HTTPHttp.

Types (classes, structs, interfaces, enums) preserve the author's exact casing and are not subject to PascalCase/camelCase transformation. UserService stays UserService, and even my_class would stay my_class.

Step 4: Reattach Leading Underscores

Reattach any leading underscores that were preserved in Step 1:

_private_field → _PrivateField (PascalCase target: methods, class fields)
_private_field → _privateField (camelCase target: parameters, locals)
__private_field → __PrivateField (PascalCase target: methods, class fields)
__private_count → __privateCount (camelCase target: parameters, locals)

Complete Examples

Sharpy Context C# Output
get_user_name Function/Method GetUserName
user_name Parameter userName
httpClient Variable httpClient (preserved)
_user_count Private field _UserCount
__private_count Private field __PrivateCount
MAX_RETRY_COUNT Constant MaxRetryCount
HTTP_STATUS_CODE Constant HttpStatusCode
get_html_parser Function GetHtmlParser
XMLParser Class (already PascalCase) XMLParser (preserved)
parse_xml Function ParseXml
io_error Parameter ioError
__init__ Constructor Constructor (special)
__str__ Dunder ToString() override
_internal_helper Private method _InternalHelper
__private_field Private field __PrivateField

Collision Handling

Name collisions can occur when different Sharpy names produce the same C# name. Note that camelCase identifiers are preserved as-is, so fooBar stays as fooBar and does not collide with foo_barFooBar.

Collisions are more likely with names that differ only in underscore placement:

# ❌ Both single-word lowercase → same PascalCase
# (hypothetical — in practice, snake_case names with different segments are unlikely to collide)

Resolution: The compiler detects collisions and reports an error. Use backtick escaping to preserve the exact identifier:

`fooBar` = 1      # Stays as 'fooBar' in C#
`foo_bar` = 2     # Stays as 'foo_bar' in C# (unusual but valid)

CamelCase Passthrough

Identifiers already in camelCase form (start lowercase, contain uppercase letters, no underscores) are preserved as-is in both PascalCase and camelCase contexts:

httpClient = create_client()   # httpClient stays as httpClient in C#
iPhone = get_device()          # iPhone stays as iPhone in C#

Rationale: camelCase names are already valid C# identifiers — mangling them would destroy the author's intended casing (e.g., httpClient would incorrectly become Httpclient). This preserves interop-friendly names and avoids surprising transformations.

Similarly, PascalCase identifiers (start uppercase, no underscores) are preserved as-is:

XMLParser = create_parser()    # XMLParser stays as XMLParser in C#
HttpClient = get_client()      # HttpClient stays as HttpClient in C#

Unrecognized Name Forms

Names that don't match any well-formed convention are passed through as-is:

  • Consecutive underscores: foo__barfoo__bar (with warning SPY0453)
  • Mixed case with underscores: Foo_barFoo_bar

Names with consecutive underscores produce a compiler warning (SPY0453) because they can cause name mangling collisions — for example, foo__bar and foo_bar would both mangle to FooBar under a naive algorithm. The compiler avoids this by passing through unrecognized forms unchanged. Mixed-case names with underscores (like Foo_bar) are also passed through but do not trigger the warning.

To suppress the warning, rename the identifier or use backtick escaping:

`foo__bar`: int = 1    # Backtick escaping: no warning, name preserved exactly

Built-in Function Name Mangling

Some Python built-in names undergo compound-word splitting during name mangling. The Sharpy source name reflects the mangled form:

Python Name Sharpy Name C# Output Reason
divmod() div_mod() DivMod() Compound-word split: divmoddiv_modDivMod

Dunder Method Mapping

Dunder methods have special mappings that don't follow the standard algorithm:

Sharpy Dunder C# Output
__init__ Constructor
__str__ ToString() override
__repr__ Custom or ToString()
__eq__ operator == and Equals()
__ne__ operator !=
__lt__ operator <
__le__ operator <=
__gt__ operator >
__ge__ operator >=
__hash__ GetHashCode()
__len__ Count property or Length
__iter__ GetEnumerator()
__getitem__ Indexer this[...]
__setitem__ Indexer setter
__contains__ Contains() method
__add__ operator +
__sub__ operator -
__mul__ operator *
__div__ operator /
__bool__ operator true/operator false

See dunder_methods.md for complete dunder specifications.

Acronym Handling

Sharpy follows a simplified acronym rule: all-caps segments are treated as single words and converted to title case:

# All-caps become title case
HTTP_STATUS  HttpStatus
XML_PARSER  XmlParser
IO_ERROR  IoError
TCP_IP_ADDRESS  TcpIpAddress

# Not preserved as all-caps (unlike some .NET conventions)
# If you need HttpClient style, use: http_client → HttpClient

Rationale: This rule is simpler to implement and produces consistent results. For names that should preserve specific casing, use backtick escaping.

Parameters vs Fields vs Methods

Different identifier types use different target conventions:

Identifier Type Convention Example
Function/Method PascalCase get_user()GetUser()
Parameter camelCase user_id: intint userId
Local variable camelCase result_countresultCount
Private field (_) _PascalCase _data_store_DataStore
Private field (__) __PascalCase __private_field__PrivateField
Public property PascalCase property user_nameUserName
Constant PascalCase MAX_SIZEMaxSize
Class/Struct/Interface Preserved UserServiceUserService
Enum value PascalCase RED_COLORRedColor

Interop with .NET Libraries

When calling .NET libraries from Sharpy, use the C# names directly (PascalCase):

# Calling .NET
from system import Console
Console.WriteLine("Hello")  # Use actual C# name

# Sharpy method calling C#
def print_message(msg: str):  # Sharpy convention
    Console.WriteLine(msg)    # C# convention

When .NET code calls Sharpy-compiled code, it sees the mangled PascalCase names:

// C# calling Sharpy-compiled code
var result = sharpyModule.GetUserName();  // Not get_user_name

Implementation - Name mangling is performed during code generation by NameMangler (form detection via NameFormDetector) - Dunder method mapping is handled by DunderMapping (codegen-owned, separate from NameMangler) - Naming convention warnings (SPY0453) are emitted by NamingConventionValidator during semantic validation - Backtick-escaped identifiers bypass mangling