Skip to content

fix: nested types remaining fixes (#591–#594) - #595

Merged
antonsynd merged 4 commits into
mainlinefrom
fix/nested-types-remaining-591-594
Apr 24, 2026
Merged

antonsynd merged 4 commits into
mainlinefrom
fix/nested-types-remaining-591-594

Conversation

@antonsynd

Copy link
Copy Markdown
Owner

Summary

Closes #591, closes #592, closes #593, closes #594

Test plan

  • All 11,602 tests pass (0 failures, 0 skipped)
  • nested_enum_public.spy — nested enum with @public decorator accessible from outside
  • nested_access_enclosing_private.spy — nested class accessing __-prefixed private member
  • nested_access_enclosing_protected.spy — nested class accessing _-prefixed protected member
  • nested_generic.spy — generic nested type Registry.Entry[int] resolves and runs (unskipped)
  • SymbolSerializer_RoundTrip_NestedTypes — serialization round-trip preserves nested types and DeclaringType
  • dataclass_on_enum error test updated for validator-level rejection

🤖 Generated with Claude Code

antonsynd and others added 4 commits April 23, 2026 22:44
Add Decorators property to EnumDef AST node, enable parser to accept
decorators on enum definitions, wire up NameResolver to read access
decorators from enums, and apply private-by-default to nested enums
in codegen (matching ClassDef/StructDef/InterfaceDef behavior).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract LookupNestedType helper in TypeResolver for dotted-name
resolution (e.g., Registry.Entry), and add it as a fallback in
ResolveGenericType so Registry.Entry[int] resolves correctly.

Also add codegen support for generic nested type constructor calls
(Outer.Inner[T](...)) by walking the TypeSymbol.NestedTypes chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…members (#593)

Add IsNestedWithin check to AccessValidator so nested types can access
their enclosing type's private and protected members, matching C#
semantics. Also fix nested type lookup during validation by falling
back to CurrentClass.NestedTypes when SymbolTable.LookupType fails
(nested types are defined in class scopes which are popped after
NameResolver completes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add NestedTypes property to CachedSymbol, serialize TypeSymbol.NestedTypes
as inline embedded CachedSymbols, restore DeclaringType from parent-child
structure during deserialization, and recursively resolve nested types'
BaseType/Interfaces in ResolveReferences. Bump schema version 10 → 11.

Also add VisitEnumDef to DecoratorValidator to validate decorators on
enums (catches @DataClass on enum with proper error message).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 24, 2026 03:45
@antonsynd
antonsynd merged commit 3344283 into mainline Apr 24, 2026
8 checks passed
@antonsynd
antonsynd deleted the fix/nested-types-remaining-591-594 branch April 24, 2026 03:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes remaining gaps in nested type support across parsing, semantic validation/resolution, codegen, and incremental compilation caching.

Changes:

  • Added decorator support for EnumDef and updated codegen so nested enums default to private unless explicitly decorated.
  • Improved semantic resolution for dotted-name nested types (including generic nested types) and updated access validation to allow nested types to access enclosing private/protected members.
  • Implemented serialization/deserialization of TypeSymbol.NestedTypes (and wiring of DeclaringType) and bumped incremental cache schema version.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/Sharpy.Compiler/Semantic/Validation/DecoratorValidator.cs Validates decorators on enums (and dataclass-on-enum error path).
src/Sharpy.Compiler/Semantic/Validation/AccessValidator.cs Allows nested types to access enclosing private/protected members; improves nested type traversal symbol lookup.
src/Sharpy.Compiler/Semantic/TypeResolver.cs Adds dotted-name nested-type lookup for both non-generic and generic type annotations.
src/Sharpy.Compiler/Semantic/NameResolver.Declarations.cs Includes EnumDef decorators when determining nested type access.
src/Sharpy.Compiler/Project/SymbolSerializer.cs Serializes/deserializes nested types and recursively resolves their references; bumps cache behavior.
src/Sharpy.Compiler/Project/SymbolCache.cs Extends cached symbol schema with NestedTypes.
src/Sharpy.Compiler/Project/IncrementalCompilationCache.cs Bumps cache schema version (10 → 11).
src/Sharpy.Compiler/Parser/Parser.cs Allows decorators on enum statements and updates related parser diagnostics.
src/Sharpy.Compiler/Parser/Ast/Statement.cs Adds Decorators to EnumDef AST node.
src/Sharpy.Compiler/CodeGen/RoslynEmitter.Expressions.Access.cs Adds codegen path for Outer.Inner[T](...) nested generic instantiation.
src/Sharpy.Compiler/CodeGen/RoslynEmitter.ClassMembers.cs Applies private-by-default behavior for nested enums unless explicitly decorated.
src/Sharpy.Compiler.Tests/Project/IncrementalCompilationTests.cs Adds serializer round-trip test for nested types.
src/Sharpy.Compiler.Tests/Integration/TestFixtures/nested_types/* Adds/updates nested-type integration fixtures; unskips nested generic test.
src/Sharpy.Compiler.Tests/Integration/TestFixtures/errors/dataclass_on_enum.error Updates expected error message for @dataclass on enums.

Comment on lines +364 to +367
foreach (var nested in nestedTypes)
{
nested.DeclaringType = symbol;
symbolRegistry[ComputeSymbolId(nested, nested.DefiningFilePath ?? cached.FilePath)] = nested;

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.

Registering nested types into symbolRegistry using ComputeSymbolId(nested, ...) inherits the same {file}:{kind}:{name} collision risk for nested types. If multiple nested types share a name within the same file, later ones will overwrite earlier ones and ResolveReferences may bind to the wrong symbol. Once IDs are made fully-qualified, this registration should use the fully-qualified ID as well.

Suggested change
foreach (var nested in nestedTypes)
{
nested.DeclaringType = symbol;
symbolRegistry[ComputeSymbolId(nested, nested.DefiningFilePath ?? cached.FilePath)] = nested;
for (var i = 0; i < nestedTypes.Count; i++)
{
var nested = nestedTypes[i];
nested.DeclaringType = symbol;
var nestedCached = cached.NestedTypes?[i];
var nestedSymbolId = !string.IsNullOrEmpty(nestedCached?.Id)
? nestedCached.Id
: ComputeSymbolId(nested, nested.DefiningFilePath ?? cached.FilePath);
symbolRegistry[nestedSymbolId] = nested;

Copilot uses AI. Check for mistakes.
Comment on lines +181 to +190
private static bool IsNestedWithin(TypeSymbol? currentClass, TypeSymbol owningType)
{
var declaring = currentClass?.DeclaringType;
while (declaring != null)
{
if (ReferenceEquals(declaring, owningType) || declaring.Name == owningType.Name)
return true;
declaring = declaring.DeclaringType;
}
return false;

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.

IsNestedWithin falls back to declaring.Name == owningType.Name. For nested-type access checks this is likely too permissive (nested types named Inner/Helper are common), and can incorrectly grant private/protected access across unrelated types that happen to share a name. Prefer strict identity (ReferenceEquals) or a stronger stable identity check (e.g., include defining file/module + declaring chain) rather than name-only matching.

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +118
public override void VisitEnumDef(EnumDef node)
{
ValidateDecorators(node.Decorators, node.Name);
ValidateDataclassOnNonClass(node.Decorators, node.Name, "enum");
base.VisitEnumDef(node);
}

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.

VisitEnumDef validates unsupported decorators and @dataclass, but it doesn't validate access-modifier decorator conflicts (e.g., @public + @private) the way member definitions do via ValidateAccessModifierDecorators. Since enums now support decorators, it would be good to reject conflicting access modifiers here (and ideally for other type definitions for consistency).

Copilot uses AI. Check for mistakes.
Comment on lines +511 to +518
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))));

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.
Comment on lines +97 to +103
// Serialize nested types as nested CachedSymbols
List<CachedSymbol>? nestedTypes = null;
if (ts.NestedTypes.Count > 0)
{
nestedTypes = ts.NestedTypes.Select(nt =>
SerializeTypeSymbol(nt, ComputeSymbolId(nt, nt.DefiningFilePath ?? filePath), nt.DefiningFilePath ?? filePath)).ToList();
}

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.

ComputeSymbolId is based on {file}:{kind}:{name}; when serializing nested types this can collide for common nested names (e.g., OuterA.Inner and OuterB.Inner in the same file both become ...:Type:Inner). That will overwrite entries in symbolRegistry and make BaseType/Interface/NestedTypes reference resolution nondeterministic for incremental compilation. Consider including the declaring type chain (e.g., OuterA.Inner) or another stable fully-qualified symbol path in the ID for nested types (and use that consistently for BaseTypeId/InterfaceEntries as well).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants