Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Decorators can only be applied to functions, classes, structs, interfaces, properties, events, or field declarations
The '@dataclass' decorator can only be applied to classes, not to enum 'Color'.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Outer:
__secret: int

def __init__(self, val: int):
self.__secret = val

@public
class Inner:
def reveal(self, outer: Outer) -> int:
return outer.__secret

def main():
o: Outer = Outer(42)
i: Outer.Inner = Outer.Inner()
print(i.reveal(o))
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
99
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Base:
_value: int

def __init__(self, val: int):
self._value = val

@public
class Helper:
def get_value(self, b: Base) -> int:
return b._value

def main():
b: Base = Base(99)
h: Base.Helper = Base.Helper()
print(h.get_value(b))
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class TrafficLight:
@public
enum State:
Red = 1
Yellow = 2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Blue
Square
Green
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Container:
@public
enum Color:
Red = 1
Blue = 2
Green = 3

enum Shape:
Circle = 1
Square = 2

def show_color(self) -> str:
return str(Container.Color.Blue)

def show_shape(self) -> str:
return str(Container.Shape.Square)

def main():
c: Container = Container()
print(c.show_color())
print(c.show_shape())
print(Container.Color.Green)

This file was deleted.

58 changes: 58 additions & 0 deletions src/Sharpy.Compiler.Tests/Project/IncrementalCompilationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2014,4 +2014,62 @@ def some_function() -> int:
}

#endregion

#region Nested Type Serialization

[Fact]
public void SymbolSerializer_RoundTrip_NestedTypes()
{
var innerType = new TypeSymbol
{
Name = "Inner",
Kind = SymbolKind.Type,
TypeKind = TypeKind.Class,
AccessLevel = AccessLevel.Private,
DefiningFilePath = "/test/nested.spy",
DeclaringFilePath = "/test/nested.spy",
DeclarationLine = 2,
DeclarationColumn = 5,
Fields = new List<VariableSymbol>
{
new VariableSymbol { Name = "value", Kind = SymbolKind.Variable, Type = BuiltinType.Int }
}
};

var outerType = new TypeSymbol
{
Name = "Outer",
Kind = SymbolKind.Type,
TypeKind = TypeKind.Class,
AccessLevel = AccessLevel.Public,
DefiningFilePath = "/test/nested.spy",
DeclaringFilePath = "/test/nested.spy",
DeclarationLine = 1,
DeclarationColumn = 1,
NestedTypes = new List<TypeSymbol> { innerType }
};
innerType.DeclaringType = outerType;

var filePath = CreateTempFile("nested.spy", "class Outer:\n class Inner:\n value: int");
var cached = SymbolSerializer.Serialize(outerType, filePath);

Assert.NotNull(cached.NestedTypes);
Assert.Single(cached.NestedTypes!);
Assert.Equal("Inner", cached.NestedTypes[0].Name);
Assert.Equal("Type", cached.NestedTypes[0].Kind);

var registry = new Dictionary<string, Symbol>();
var restored = SymbolSerializer.Deserialize(cached, registry) as TypeSymbol;

Assert.NotNull(restored);
Assert.Single(restored!.NestedTypes);
Assert.Equal("Inner", restored.NestedTypes[0].Name);
Assert.Equal(TypeKind.Class, restored.NestedTypes[0].TypeKind);
Assert.Equal(AccessLevel.Private, restored.NestedTypes[0].AccessLevel);
Assert.Equal(restored, restored.NestedTypes[0].DeclaringType);
Assert.Single(restored.NestedTypes[0].Fields);
Assert.Equal("value", restored.NestedTypes[0].Fields[0].Name);
}

#endregion
}
5 changes: 4 additions & 1 deletion src/Sharpy.Compiler/CodeGen/RoslynEmitter.ClassMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,13 @@ private List<MemberDeclarationSyntax> GenerateClassMembers(
break;

case EnumDef nestedEnum:
// EnumDef lacks Decorators — keep public until parser supports access decorators on enums (#592)
var nestedEnumNode = GenerateEnumDeclaration(nestedEnum);
if (nestedEnumNode is MemberDeclarationSyntax nestedEnumMember)
{
if (!HasExplicitAccessDecorator(nestedEnum.Decorators))
nestedEnumMember = ReplaceAccessModifier(nestedEnumMember, SyntaxKind.PrivateKeyword);
members.Add(nestedEnumMember);
}
break;

default:
Expand Down
59 changes: 59 additions & 0 deletions src/Sharpy.Compiler/CodeGen/RoslynEmitter.Expressions.Access.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ private ExpressionSyntax GenerateCall(FunctionCall call)
return result;
}

// Handle generic nested type instantiation: Outer.Inner[int](42)
if (call.Function is IndexAccess nestedIndexAccess &&
nestedIndexAccess.Object is MemberAccess nestedMemberAccess)
{
var result = GenerateNestedGenericInstantiation(nestedIndexAccess, nestedMemberAccess, call);
if (result != null)
return result;
}

if (call.Function is Identifier funcName)
{
// Check if this is a builtin function call (e.g., int(), str(), print(), len(), etc.)
Expand Down Expand Up @@ -492,6 +501,56 @@ symbol is TypeSymbol typeSymbol &&
return null;
}

private ExpressionSyntax? GenerateNestedGenericInstantiation(
IndexAccess indexAccess, MemberAccess memberAccess, FunctionCall call)
{
var nestedTypeSymbol = LookupNestedTypeFromMemberAccess(memberAccess);
if (nestedTypeSymbol == null || !nestedTypeSymbol.IsGeneric)
return null;

var typeArgsSyntax = _typeMapper.MapTypeArgumentsFromExpression(indexAccess.Index);
var csharpName = NameMangler.ToPascalCase(memberAccess.Member);
var outerName = GetNestedTypeOuterPrefix(nestedTypeSymbol);
var qualifiedGenericName = QualifiedName(
ParseName(outerName),
GenericName(csharpName)
.WithTypeArgumentList(TypeArgumentList(SeparatedList(typeArgsSyntax))));

Comment on lines +511 to +518

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested generic instantiation path uses NameMangler.ToPascalCase and a string-based outerName + ParseName(...) to build the type name. This can diverge from existing nested-type codegen (BuildNestedTypeName) which uses NameMangler.Transform(..., NameContext.Type) to handle keywords/backtick-escaped identifiers consistently. Consider reusing BuildNestedTypeName (or the same NameMangler.Transform logic) when forming qualifiedGenericName so nested generic types emit the same mangled names as non-generic nested types.

Copilot uses AI. Check for mistakes.
var constructorTarget = ResolveConstructorForCall(nestedTypeSymbol, call);
var allArgs = GenerateReorderedCallArguments(call, constructorTarget);

return ObjectCreationExpression(qualifiedGenericName)
.WithArgumentList(ArgumentList(SeparatedList(allArgs)));
}

private TypeSymbol? LookupNestedTypeFromMemberAccess(MemberAccess memberAccess)
{
if (memberAccess.Object is Identifier outerName)
{
var outerSymbol = _context.LookupSymbol(outerName.Name) as TypeSymbol;
return outerSymbol?.NestedTypes.FirstOrDefault(n => n.Name == memberAccess.Member);
}
if (memberAccess.Object is MemberAccess innerAccess)
{
var parentType = LookupNestedTypeFromMemberAccess(innerAccess);
return parentType?.NestedTypes.FirstOrDefault(n => n.Name == memberAccess.Member);
}
return null;
}

private static string GetNestedTypeOuterPrefix(TypeSymbol nestedType)
{
var parts = new List<string>();
var declaring = nestedType.DeclaringType;
while (declaring != null)
{
parts.Add(NameMangler.ToPascalCase(declaring.Name));
declaring = declaring.DeclaringType;
}
parts.Reverse();
return string.Join(".", parts);
}

/// <summary>
/// For DefaultDict construction, wraps type-reference arguments in factory lambdas.
/// <c>defaultdict[str, list[int]](list)</c> becomes
Expand Down
2 changes: 2 additions & 0 deletions src/Sharpy.Compiler/Parser/Ast/Statement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -604,13 +604,15 @@ public record EnumDef : Statement
public bool IsNameBacktickEscaped { get; init; }
public ImmutableArray<EnumMember> Members { get; init; } = ImmutableArray<EnumMember>.Empty;
public string? DocString { get; init; }
public ImmutableArray<Decorator> Decorators { get; init; } = ImmutableArray<Decorator>.Empty;

/// <inheritdoc/>
public override void ValidateInvariants()
{
base.ValidateInvariants();
Debug.Assert(!string.IsNullOrEmpty(Name), "EnumDef.Name cannot be null or empty");
Debug.Assert(Members != null, "EnumDef.Members cannot be null");
Debug.Assert(Decorators != null, "EnumDef.Decorators cannot be null");
}

/// <inheritdoc/>
Expand Down
8 changes: 5 additions & 3 deletions src/Sharpy.Compiler/Parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,12 @@ private Statement ParseDecoratedStatement()
TokenType.Struct => ParseStructDef(),
TokenType.Interface => ParseInterfaceDef(),
TokenType.Union => ParseUnionDef(),
TokenType.Enum => ParseEnumDef(),
TokenType.Property => ParsePropertyDef(),
TokenType.Event => ParseEventDef(),
// Allow decorators on variable declarations (e.g., @static field in class body)
TokenType.Identifier => ParseSimpleStatement(),
_ => throw ReportError("Decorators can only be applied to functions, classes, structs, interfaces, properties, events, or field declarations", Current.Line, Current.Column, DiagnosticCodes.Parser.InvalidDecoratorTarget, span: CurrentSpan)
_ => throw ReportError("Decorators can only be applied to functions, classes, structs, interfaces, enums, properties, events, or field declarations", Current.Line, Current.Column, DiagnosticCodes.Parser.InvalidDecoratorTarget, span: CurrentSpan)
};
}
finally
Expand All @@ -519,11 +520,12 @@ private Statement ParseDecoratedStatement()
StructDef str => str with { Decorators = decorators.ToImmutableArray() },
InterfaceDef iface => iface with { Decorators = decorators.ToImmutableArray() },
UnionDef union => union with { Decorators = decorators.ToImmutableArray() },
EnumDef en => en with { Decorators = decorators.ToImmutableArray() },
PropertyDef prop => prop with { Decorators = decorators.ToImmutableArray() },
EventDef ev => ev with { Decorators = decorators.ToImmutableArray() },
VariableDeclaration varDecl => varDecl with { Decorators = decorators.ToImmutableArray() },
Assignment => throw ReportError("Decorators cannot be applied to assignments — only functions, classes, structs, interfaces, properties, events, or field declarations", stmt.LineStart, stmt.ColumnStart, DiagnosticCodes.Parser.InvalidDecoratorTarget, span: stmt.Span),
_ => throw ReportError("Decorators can only be applied to functions, classes, structs, interfaces, properties, events, or field declarations", stmt.LineStart, stmt.ColumnStart, DiagnosticCodes.Parser.InvalidDecoratorTarget, span: stmt.Span)
Assignment => throw ReportError("Decorators cannot be applied to assignments — only functions, classes, structs, interfaces, enums, properties, events, or field declarations", stmt.LineStart, stmt.ColumnStart, DiagnosticCodes.Parser.InvalidDecoratorTarget, span: stmt.Span),
_ => throw ReportError("Decorators can only be applied to functions, classes, structs, interfaces, enums, properties, events, or field declarations", stmt.LineStart, stmt.ColumnStart, DiagnosticCodes.Parser.InvalidDecoratorTarget, span: stmt.Span)
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/Sharpy.Compiler/Project/IncrementalCompilationCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal class IncrementalCompilationCache
/// Current schema version for the symbol cache.
/// Increment this when making breaking changes to FileCacheEntry or CachedSymbol structures.
/// </summary>
internal const int CurrentSchemaVersion = 10;
internal const int CurrentSchemaVersion = 11;

private readonly string _cacheFilePath;
private readonly string _symbolCachePath;
Expand Down
5 changes: 5 additions & 0 deletions src/Sharpy.Compiler/Project/SymbolCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ internal record CachedSymbol
/// </summary>
public List<CachedSymbol>? Constructors { get; init; }

/// <summary>
/// For TypeSymbol: nested types (serialized as CachedSymbol with Kind=Type)
/// </summary>
public List<CachedSymbol>? NestedTypes { get; init; }

/// <summary>
/// For FunctionSymbol: parameters
/// </summary>
Expand Down
Loading
Loading