Skip to content

Commit 6f42de8

Browse files
antonsyndclaude
andcommitted
fix: Add variadic parameter support and fix type conversion functions
- Add IsVariadic property to ParameterSymbol for params array handling - Update CachedModuleDiscovery to extract element type from variadic parameters - Update overload resolution in TypeChecker to handle variadic functions - Filter candidates properly considering variadic arg count (0+ args) - Type check variadic args against element type, not array type - Fix code generation to treat builtin functions as invocations, not constructors - int(x), str(x), etc. now emit function calls, not 'new' expressions - Fix SymbolTable to not add function symbols that shadow type names - Prevents int function from overwriting int type in symbol table This fixes: - print() accepting any number of arguments (variadic) - int(), float(), str() type conversion functions working properly - Builtin function overload resolution with multiple candidates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b580596 commit 6f42de8

File tree

6 files changed

+85
-18
lines changed

6 files changed

+85
-18
lines changed

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,24 @@ private ExpressionSyntax GenerateCall(FunctionCall call)
134134

135135
if (call.Function is Identifier funcName)
136136
{
137+
// Check if this is a builtin function call (e.g., int(), str(), print(), len(), etc.)
138+
// Builtin functions are always invocation expressions, never constructor calls.
139+
var isBuiltinFunc = _context.IsBuiltinFunction(funcName.Name);
140+
137141
// Check if this is a type instantiation (calling a class or struct constructor)
138142
// We check both:
139143
// 1. The _classNames and _structNames sets (populated during type declaration generation)
140144
// 2. The symbol table (for testing and imported types)
145+
// BUT: If it's a builtin function, it's NOT a type instantiation (e.g., int(x) is a conversion function)
141146
var symbol = _context.LookupSymbol(funcName.Name);
142-
var isTypeInstantiation = _classNames.Contains(funcName.Name) ||
143-
_structNames.Contains(funcName.Name) ||
144-
(symbol is TypeSymbol typeSymbol &&
145-
(typeSymbol.TypeKind == Semantic.TypeKind.Class ||
146-
typeSymbol.TypeKind == Semantic.TypeKind.Struct));
147-
148-
var name = _context.IsBuiltinFunction(funcName.Name)
147+
var isTypeInstantiation = !isBuiltinFunc &&
148+
(_classNames.Contains(funcName.Name) ||
149+
_structNames.Contains(funcName.Name) ||
150+
(symbol is TypeSymbol typeSymbol &&
151+
(typeSymbol.TypeKind == Semantic.TypeKind.Class ||
152+
typeSymbol.TypeKind == Semantic.TypeKind.Struct)));
153+
154+
var name = isBuiltinFunc
149155
? $"global::Sharpy.Core.Exports.{NameMangler.ToPascalCase(funcName.Name)}"
150156
: NameMangler.ToPascalCase(funcName.Name);
151157

src/Sharpy.Compiler/Discovery/CachedModuleDiscovery.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,34 @@ private FunctionSymbol ConvertToFunctionSymbol(FunctionSignature signature, stri
116116
.Select(p => new ParameterSymbol
117117
{
118118
Name = p.Name,
119-
Type = ConvertTypeSignature(p.Type),
119+
// For variadic parameters, extract the element type from list[T] -> T
120+
Type = p.IsVariadic ? GetVariadicElementType(p.Type) : ConvertTypeSignature(p.Type),
120121
HasDefault = p.HasDefault,
121122
// Note: DefaultValue Expression reconstruction is simplified
122-
DefaultValue = null // TODO: Reconstruct from cached string
123+
DefaultValue = null, // TODO: Reconstruct from cached string
124+
IsVariadic = p.IsVariadic
123125
})
124126
.ToList(),
125127
AccessLevel = AccessLevel.Public
126128
};
127129
}
128130

131+
/// <summary>
132+
/// Extract the element type from a variadic parameter's type signature.
133+
/// Variadic parameters are stored as list[T] but we need just T for type checking.
134+
/// </summary>
135+
private SemanticType GetVariadicElementType(TypeSignature typeSignature)
136+
{
137+
// Variadic parameters are stored as list[T] (mapped from T[])
138+
// We need to extract T
139+
if (typeSignature.IsGeneric && typeSignature.Name.StartsWith("list") && typeSignature.TypeArguments.Count == 1)
140+
{
141+
return ConvertTypeSignature(typeSignature.TypeArguments[0]);
142+
}
143+
// Fallback to the full type if not a list
144+
return ConvertTypeSignature(typeSignature);
145+
}
146+
129147
/// <summary>
130148
/// Convert a TypeSignature back to a SemanticType.
131149
/// </summary>

src/Sharpy.Compiler/Discovery/Caching/OverloadIndexBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ private ModuleOverloads DiscoverModuleFunctions(Type exportType)
7272
.Where(m => !m.Name.StartsWith("set_"))
7373
.Where(m => !m.IsSpecialName)
7474
.Where(m => !m.IsGenericMethodDefinition) // Skip generic methods for now
75-
// Note: Type constructors (Int, Bool, Str, etc.) are included as they are
76-
// valid builtin functions that can be called for type conversion.
75+
// Note: Type constructors (Int, Bool, Str, etc.) are included as they are
76+
// valid builtin functions that can be called for type conversion.
7777
.ToList();
7878

7979
// Group by function name

src/Sharpy.Compiler/Semantic/Symbol.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ public record ParameterSymbol
113113
public SemanticType Type { get; init; } = SemanticType.Unknown;
114114
public bool HasDefault { get; init; }
115115
public Expression? DefaultValue { get; init; }
116+
/// <summary>
117+
/// If true, this parameter accepts a variable number of arguments (params array).
118+
/// The Type property contains the element type of the params array.
119+
/// </summary>
120+
public bool IsVariadic { get; init; }
116121
}
117122

118123
/// <summary>

src/Sharpy.Compiler/Semantic/SymbolTable.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,22 @@ public SymbolTable(BuiltinRegistry builtins)
2222
private void PopulateBuiltins()
2323
{
2424
// Add builtin types
25+
var typeNames = new HashSet<string>();
2526
foreach (var (name, typeSymbol) in _builtins.GetAllTypes())
2627
{
2728
_globalScope.Define(typeSymbol);
29+
typeNames.Add(name);
2830
}
2931

3032
// Add builtin functions (only add one symbol per function name, overload resolution happens later)
3133
// For functions with multiple overloads, only the first overload is added to the global scope.
3234
// The TypeChecker uses BuiltinRegistry.GetFunctionOverloads() for proper overload resolution.
35+
// Skip functions that have the same name as types (e.g., int(), str(), bool()) - these are
36+
// handled by TypeChecker which routes primitive type "constructor" calls to builtin function overloads.
3337
var addedFunctions = new HashSet<string>();
3438
foreach (var (name, funcSymbol) in _builtins.GetAllFunctions())
3539
{
36-
if (!addedFunctions.Contains(name))
40+
if (!addedFunctions.Contains(name) && !typeNames.Contains(name))
3741
{
3842
_globalScope.Define(funcSymbol);
3943
addedFunctions.Add(name);

src/Sharpy.Compiler/Semantic/TypeChecker.Expressions.cs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -789,15 +789,25 @@ indexAccess.Object is Identifier genericTypeId &&
789789
}
790790

791791
// Special handling for builtin functions with overloads
792-
// If there is exactly one overload, it will be handled by the regular function symbol validation below.
792+
// When there are multiple overloads, we need to perform overload resolution to find the right one.
793+
// The funcSymbol from Lookup is just the first overload, which may not match the call.
794+
// Only use builtin overloads if there's no user-defined function shadowing the builtin.
793795
var overloads = _symbolTable.BuiltinRegistry.GetFunctionOverloads(id.Name);
794-
if (overloads != null && overloads.Count > 1)
796+
var isBuiltinWithOverloads = overloads != null && overloads.Count > 1;
797+
// If funcSymbol was found in symbol table AND it's one of the builtin overloads, use overload resolution
798+
var needsOverloadResolution = isBuiltinWithOverloads &&
799+
(funcSymbol == null || (funcSymbol != null && overloads!.Contains(funcSymbol)));
800+
if (needsOverloadResolution)
795801
{
796-
// First pass: filter by argument count (considering default parameters)
802+
// First pass: filter by argument count (considering default parameters and variadic parameters)
797803
var candidateOverloads = overloads.Where(o =>
798804
{
799-
var requiredParams = o.Parameters.Count(p => !p.HasDefault);
805+
var requiredParams = o.Parameters.Count(p => !p.HasDefault && !p.IsVariadic);
806+
var hasVariadic = o.Parameters.Any(p => p.IsVariadic);
800807
var totalParams = o.Parameters.Count;
808+
// Variadic functions can accept any number of arguments >= required
809+
if (hasVariadic)
810+
return totalArgCount >= requiredParams;
801811
return totalArgCount >= requiredParams && totalArgCount <= totalParams;
802812
}).ToList();
803813

@@ -806,9 +816,30 @@ indexAccess.Object is Identifier genericTypeId &&
806816
foreach (var overload in candidateOverloads)
807817
{
808818
bool typesMatch = true;
819+
var hasVariadic = overload.Parameters.Any(p => p.IsVariadic);
820+
var variadicParam = overload.Parameters.FirstOrDefault(p => p.IsVariadic);
821+
809822
for (int i = 0; i < argTypes.Count; i++)
810823
{
811-
if (!IsAssignable(argTypes[i], overload.Parameters[i].Type))
824+
SemanticType expectedType;
825+
if (i < overload.Parameters.Count && !overload.Parameters[i].IsVariadic)
826+
{
827+
// Regular parameter
828+
expectedType = overload.Parameters[i].Type;
829+
}
830+
else if (variadicParam != null)
831+
{
832+
// Variadic parameter - all remaining args must match the element type
833+
expectedType = variadicParam.Type;
834+
}
835+
else
836+
{
837+
// Index out of bounds - shouldn't happen with valid candidates
838+
typesMatch = false;
839+
break;
840+
}
841+
842+
if (!IsAssignable(argTypes[i], expectedType))
812843
{
813844
typesMatch = false;
814845
break;
@@ -832,8 +863,11 @@ indexAccess.Object is Identifier genericTypeId &&
832863
// No matching overload found
833864
var expectedCounts = string.Join(" or ", overloads.Select(o =>
834865
{
835-
var required = o.Parameters.Count(p => !p.HasDefault);
866+
var required = o.Parameters.Count(p => !p.HasDefault && !p.IsVariadic);
836867
var total = o.Parameters.Count;
868+
var hasVariadic = o.Parameters.Any(p => p.IsVariadic);
869+
if (hasVariadic)
870+
return $"{required}+";
837871
return required == total ? total.ToString() : $"{required}-{total}";
838872
}).Distinct());
839873
AddError($"Function '{id.Name}' expects {expectedCounts} arguments but got {totalArgCount}",

0 commit comments

Comments
 (0)