Skip to content

Commit 27c5fc1

Browse files
antonsyndclaude
andcommitted
fix(semantic+codegen): complete __getitem__/__setitem__ support for user-defined types (#279)
- Fix TypeInferenceService to check ProtocolMethods (not just OperatorMethods) for __getitem__, since container dunders are protocol dunders - Add codegen for IndexAccess on user-defined types: emit obj.GetItem(key) instead of obj[key] since user-defined classes don't have C# indexer properties - Add codegen for index assignment on user-defined types: emit obj.SetItem(key, value) instead of obj[key] = value - Remove dunder_container.skip now that the test passes end-to-end Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8330880 commit 27c5fc1

4 files changed

Lines changed: 45 additions & 5 deletions

File tree

src/Sharpy.Compiler.Tests/Integration/TestFixtures/classes/dunder_container.skip

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/Sharpy.Compiler/CodeGen/RoslynEmitter.Expressions.Access.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,12 +877,26 @@ private ExpressionSyntax GenerateIndexAccess(IndexAccess indexAccess)
877877
var objExpr = GenerateExpression(indexAccess.Object);
878878
var index = GenerateExpression(indexAccess.Index);
879879

880+
// User-defined types with __getitem__: emit obj.GetItem(key) instead of obj[key]
881+
// because user-defined classes don't have a C# indexer property.
882+
var objectType = GetExpressionSemanticType(indexAccess.Object);
883+
if (objectType is Semantic.UserDefinedType udt && udt.Symbol != null &&
884+
(udt.Symbol.ProtocolMethods.ContainsKey(Semantic.DunderNames.GetItem) ||
885+
udt.Symbol.OperatorMethods.ContainsKey(Semantic.DunderNames.GetItem)))
886+
{
887+
return InvocationExpression(
888+
MemberAccessExpression(
889+
SyntaxKind.SimpleMemberAccessExpression,
890+
objExpr,
891+
IdentifierName("GetItem")))
892+
.AddArgumentListArguments(Argument(index));
893+
}
894+
880895
var elementAccess = ElementAccessExpression(objExpr)
881896
.AddArgumentListArguments(Argument(index));
882897

883898
// String indexing: C# string[int] returns char, but Sharpy types it as str.
884899
// Wrap with .ToString() to bridge the type gap.
885-
var objectType = GetExpressionSemanticType(indexAccess.Object);
886900
if (objectType == Semantic.SemanticType.Str)
887901
{
888902
return InvocationExpression(

src/Sharpy.Compiler/CodeGen/RoslynEmitter.Statements.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,19 +395,45 @@ private StatementSyntax GenerateAssignment(Assignment assign)
395395
var obj = GenerateExpression(indexAccess.Object);
396396
var index = GenerateExpression(indexAccess.Index);
397397

398+
// User-defined types with __setitem__: emit obj.SetItem(key, value)
399+
var targetObjType = GetExpressionSemanticType(indexAccess.Object);
400+
if (targetObjType is Semantic.UserDefinedType setUdt && setUdt.Symbol != null &&
401+
(setUdt.Symbol.ProtocolMethods.ContainsKey(Semantic.DunderNames.SetItem) ||
402+
setUdt.Symbol.OperatorMethods.ContainsKey(Semantic.DunderNames.SetItem)))
403+
{
404+
var assignmentValue = assign.Operator == AssignmentOperator.Assign
405+
? value
406+
: GenerateAugmentedValue(assign.Operator,
407+
InvocationExpression(
408+
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
409+
obj, IdentifierName("GetItem")))
410+
.AddArgumentListArguments(Argument(index)),
411+
value, assign.Target, assign.Value);
412+
413+
return ExpressionStatement(
414+
InvocationExpression(
415+
MemberAccessExpression(
416+
SyntaxKind.SimpleMemberAccessExpression,
417+
obj,
418+
IdentifierName("SetItem")))
419+
.AddArgumentListArguments(
420+
Argument(index),
421+
Argument(assignmentValue)));
422+
}
423+
398424
var elementAccess = ElementAccessExpression(obj)
399425
.WithArgumentList(BracketedArgumentList(
400426
SingletonSeparatedList(Argument(index))));
401427

402-
var assignmentValue = assign.Operator == AssignmentOperator.Assign
428+
var augmentedValue = assign.Operator == AssignmentOperator.Assign
403429
? value
404430
: GenerateAugmentedValue(assign.Operator, elementAccess, value, assign.Target, assign.Value);
405431

406432
return ExpressionStatement(
407433
AssignmentExpression(
408434
SyntaxKind.SimpleAssignmentExpression,
409435
elementAccess,
410-
assignmentValue));
436+
augmentedValue));
411437
}
412438

413439
// Handle member assignment: obj.field = value

src/Sharpy.Compiler/Semantic/TypeInferenceService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,8 @@ c.Parameters[1].Type is GenericType paramGeneric &&
730730
};
731731

732732
if (typeSymbol != null &&
733-
typeSymbol.OperatorMethods.TryGetValue(DunderNames.GetItem, out var getItemMethods))
733+
(typeSymbol.OperatorMethods.TryGetValue(DunderNames.GetItem, out var getItemMethods) ||
734+
typeSymbol.ProtocolMethods.TryGetValue(DunderNames.GetItem, out getItemMethods)))
734735
{
735736
var bestOverload = FindBestOverload(getItemMethods, index);
736737
if (bestOverload != null)

0 commit comments

Comments
 (0)