Add literal conversion based on context at compile time

This commit is contained in:
nub31
2025-06-07 20:34:10 +02:00
parent 49c4e94515
commit 3c3a48d3b4
10 changed files with 618 additions and 600 deletions

View File

@@ -1,5 +1,17 @@
namespace main namespace main
struct Human {
age: ^u64
}
export func main(args: []^string) { export func main(args: []^string) {
sys::call(60, 0) let x = [3]f64
}
x[0] = 1
x[1.2] = 2
x[2] = 3
c::printf("%d\n", x[0])
c::printf("%d\n", x[1])
c::printf("%d\n", x[2])
}

File diff suppressed because it is too large Load Diff

View File

@@ -171,12 +171,6 @@ public class Lexer
buffer += next; buffer += next;
Next(); Next();
} }
else if (next == 'f')
{
isFloat = true;
Next();
break;
}
else else
{ {
break; break;

View File

@@ -20,37 +20,6 @@ public interface IFuncSignature
public List<FuncParameter> Parameters { get; } public List<FuncParameter> Parameters { get; }
public Optional<NubType> ReturnType { get; } public Optional<NubType> ReturnType { get; }
public bool SignatureMatches(string name, List<NubType> parameters)
{
if (Name != name) return false;
if (Parameters.Count == 0 && parameters.Count == 0) return true;
if (Parameters.Count > parameters.Count) return false;
for (var i = 0; i < parameters.Count; i++)
{
if (i >= Parameters.Count)
{
if (Parameters.Count > 0 && Parameters[^1].Variadic)
{
if (!NubType.IsCompatibleWith(parameters[i], Parameters[^1].Type))
{
return false;
}
}
else
{
return false;
}
}
else if (!NubType.IsCompatibleWith(parameters[i], Parameters[i].Type))
{
return false;
}
}
return true;
}
public string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}"; public string ToString() => $"{Name}({string.Join(", ", Parameters.Select(p => p.ToString()))}){(ReturnType.HasValue ? ": " + ReturnType.Value : "")}";
} }

View File

@@ -1,10 +1,10 @@
using Nub.Lang.Frontend.Lexing; // using Nub.Lang.Frontend.Lexing;
using Nub.Lang.Frontend.Typing; // using Nub.Lang.Frontend.Typing;
//
namespace Nub.Lang.Frontend.Parsing.Expressions; // namespace Nub.Lang.Frontend.Parsing.Expressions;
//
public class CastNode(IReadOnlyList<Token> tokens, NubType targetType, ExpressionNode expression) : ExpressionNode(tokens) // public class CastNode(IReadOnlyList<Token> tokens, NubType targetType, ExpressionNode expression) : ExpressionNode(tokens)
{ // {
public NubType TargetType { get; } = targetType; // public NubType TargetType { get; } = targetType;
public ExpressionNode Expression { get; } = expression; // public ExpressionNode Expression { get; } = expression;
} // }

View File

@@ -8,5 +8,5 @@ public class FuncCallNode(IReadOnlyList<Token> tokens, string @namespace, string
public string Name { get; } = name; public string Name { get; } = name;
public List<ExpressionNode> Parameters { get; } = parameters; public List<ExpressionNode> Parameters { get; } = parameters;
public override string ToString() => $"{Name}()"; public override string ToString() => $"{Name}::{Name}()";
} }

View File

@@ -1,5 +1,4 @@
using Nub.Lang.Frontend.Lexing; using Nub.Lang.Frontend.Lexing;
using Nub.Lang.Frontend.Typing;
namespace Nub.Lang.Frontend.Parsing.Expressions; namespace Nub.Lang.Frontend.Parsing.Expressions;

View File

@@ -499,16 +499,16 @@ public class Parser
expr = expression; expr = expression;
break; break;
} }
case Symbol.LessThan: // case Symbol.LessThan:
{ // {
var type = ParseType(); // var type = ParseType();
ExpectSymbol(Symbol.GreaterThan); // ExpectSymbol(Symbol.GreaterThan);
ExpectSymbol(Symbol.OpenParen); // ExpectSymbol(Symbol.OpenParen);
var expressionToCast = ParseExpression(); // var expressionToCast = ParseExpression();
ExpectSymbol(Symbol.CloseParen); // ExpectSymbol(Symbol.CloseParen);
expr = new CastNode(GetTokensForNode(startIndex), type, expressionToCast); // expr = new CastNode(GetTokensForNode(startIndex), type, expressionToCast);
break; // break;
} // }
case Symbol.Ampersand: case Symbol.Ampersand:
{ {
var expression = ParsePrimaryExpression(); var expression = ParsePrimaryExpression();

View File

@@ -24,6 +24,30 @@ public abstract class NubType
return false; return false;
} }
public bool IsInteger => this is NubPrimitiveType
{
Kind: PrimitiveTypeKind.I8
or PrimitiveTypeKind.I16
or PrimitiveTypeKind.I32
or PrimitiveTypeKind.I64
or PrimitiveTypeKind.U8
or PrimitiveTypeKind.U16
or PrimitiveTypeKind.U32
or PrimitiveTypeKind.U64
};
public bool IsFloat32 => this is NubPrimitiveType
{
Kind: PrimitiveTypeKind.F32
};
public bool IsFloat64 => this is NubPrimitiveType
{
Kind: PrimitiveTypeKind.F64
};
public bool IsNumber => IsFloat32 || IsFloat64 || IsInteger;
public abstract override bool Equals(object? obj); public abstract override bool Equals(object? obj);
public abstract override int GetHashCode(); public abstract override int GetHashCode();
public abstract override string ToString(); public abstract override string ToString();

View File

@@ -71,7 +71,7 @@ public class TypeChecker
if (field.Value.HasValue) if (field.Value.HasValue)
{ {
var fieldType = TypeCheckExpression(field.Value.Value); var fieldType = TypeCheckExpression(field.Value.Value, field.Type);
if (fieldType != null && !fieldType.Equals(field.Type)) if (fieldType != null && !fieldType.Equals(field.Type))
{ {
ReportError("Default field initializer does not match the defined type", field.Value.Value); ReportError("Default field initializer does not match the defined type", field.Value.Value);
@@ -153,7 +153,7 @@ public class TypeChecker
{ {
var memberType = TypeCheckExpression(memberAssignment.MemberAccess); var memberType = TypeCheckExpression(memberAssignment.MemberAccess);
if (memberType == null) return; if (memberType == null) return;
var valueType = TypeCheckExpression(memberAssignment.Value); var valueType = TypeCheckExpression(memberAssignment.Value, memberType);
if (valueType == null) return; if (valueType == null) return;
if (!NubType.IsCompatibleWith(memberType, valueType)) if (!NubType.IsCompatibleWith(memberType, valueType))
@@ -166,7 +166,7 @@ public class TypeChecker
{ {
var itemType = TypeCheckExpression(arrayIndexAssignment.ArrayIndexAccess); var itemType = TypeCheckExpression(arrayIndexAssignment.ArrayIndexAccess);
if (itemType == null) return; if (itemType == null) return;
var valueType = TypeCheckExpression(arrayIndexAssignment.Value); var valueType = TypeCheckExpression(arrayIndexAssignment.Value, itemType);
if (valueType == null) return; if (valueType == null) return;
if (!NubType.IsCompatibleWith(itemType, valueType)) if (!NubType.IsCompatibleWith(itemType, valueType))
@@ -177,19 +177,18 @@ public class TypeChecker
private void TypeCheckVariableAssignment(VariableAssignmentNode variableAssignment) private void TypeCheckVariableAssignment(VariableAssignmentNode variableAssignment)
{ {
var valueType = TypeCheckExpression(variableAssignment.Value); if (!_variables.TryGetValue(variableAssignment.Identifier.Identifier, out var variable))
if (valueType == null) return;
if (!_variables.TryGetValue(variableAssignment.Identifier.Identifier, out var existingVariable))
{ {
ReportError($"Variable '{variableAssignment.Identifier}' is not declared", variableAssignment); ReportError($"Variable '{variableAssignment.Identifier}' is not declared", variableAssignment);
return; return;
} }
var valueType = TypeCheckExpression(variableAssignment.Value, variable);
if (valueType == null) return;
if (!NubType.IsCompatibleWith(variableAssignment.Value.Type, existingVariable)) if (!NubType.IsCompatibleWith(variableAssignment.Value.Type, variable))
{ {
ReportError($"Cannot assign expression of type '{variableAssignment.Value.Type}' to variable '{variableAssignment.Identifier}' with type '{existingVariable}'", ReportError($"Cannot assign expression of type '{variableAssignment.Value.Type}' to variable '{variableAssignment.Identifier}' with type '{variable}'", variableAssignment);
variableAssignment);
} }
} }
@@ -197,14 +196,14 @@ public class TypeChecker
{ {
NubType? type = null; NubType? type = null;
if (_variables.TryGetValue(variableDeclaration.Name, out var existingVariable)) if (_variables.TryGetValue(variableDeclaration.Name, out var variable))
{ {
ReportError($"Cannot redeclare variable '{existingVariable}'", variableDeclaration); ReportError($"Cannot redeclare variable '{variable}'", variableDeclaration);
} }
if (variableDeclaration.Value.HasValue) if (variableDeclaration.Value.HasValue)
{ {
var valueType = TypeCheckExpression(variableDeclaration.Value.Value); var valueType = TypeCheckExpression(variableDeclaration.Value.Value, variableDeclaration.ExplicitType.Value);
if (valueType == null) return; if (valueType == null) return;
type = valueType; type = valueType;
} }
@@ -254,31 +253,21 @@ public class TypeChecker
private NubType? TypeCheckFuncCall(FuncCallNode funcCall, Node node) private NubType? TypeCheckFuncCall(FuncCallNode funcCall, Node node)
{ {
List<NubType> parameterTypes = []; var funcDefinition = LookupFuncSignature(funcCall.Namespace, funcCall.Name);
foreach (var funcCallParameter in funcCall.Parameters)
{
var parameterType = TypeCheckExpression(funcCallParameter);
if (parameterType == null) return null;
parameterTypes.Add(parameterType);
}
var funcDefinition = LookupFuncSignature(funcCall.Namespace, funcCall.Name, parameterTypes);
if (funcDefinition == null) if (funcDefinition == null)
{ {
ReportError($"Function '{funcCall.Name}' is not defined", node); ReportError($"Function '{funcCall}' is not defined", node);
return null; return null;
} }
if (funcDefinition.Parameters.Take(funcDefinition.Parameters.Count - 1).Any(x => x.Variadic)) if (funcDefinition.Parameters.Take(funcDefinition.Parameters.Count - 1).Any(x => x.Variadic))
{ {
ReportError($"Function '{funcCall.Name}' has multiple variadic parameters", node); ReportError($"Function '{funcCall}' has multiple variadic parameters", node);
return null; return null;
} }
for (var i = 0; i < funcCall.Parameters.Count; i++) for (var i = 0; i < funcCall.Parameters.Count; i++)
{ {
var argType = funcCall.Parameters[i].Type;
NubType paramType; NubType paramType;
if (i < funcDefinition.Parameters.Count) if (i < funcDefinition.Parameters.Count)
{ {
@@ -290,13 +279,16 @@ public class TypeChecker
} }
else else
{ {
ReportError($"Function '{funcCall.Name}' does not take {funcCall.Parameters.Count} parameters", node); ReportError($"Function '{funcCall}' does not take {funcCall.Parameters.Count} parameters", node);
continue; continue;
} }
var argType = TypeCheckExpression(funcCall.Parameters[i], paramType);
if (argType == null) return null;
if (!NubType.IsCompatibleWith(argType, paramType)) if (!NubType.IsCompatibleWith(argType, paramType))
{ {
ReportError($"Parameter {i + 1} of function '{funcCall.Name}' expects type '{paramType}', but got '{argType}'", funcCall.Parameters[i]); ReportError($"Parameter {i + 1} of function '{funcCall}' expects type '{paramType}', but got '{argType}'", funcCall.Parameters[i]);
} }
} }
@@ -305,7 +297,7 @@ public class TypeChecker
private void TypeCheckIf(IfNode ifNode) private void TypeCheckIf(IfNode ifNode)
{ {
var conditionType = TypeCheckExpression(ifNode.Condition); var conditionType = TypeCheckExpression(ifNode.Condition, NubPrimitiveType.Bool);
if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool)) if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
{ {
ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition); ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition);
@@ -322,7 +314,7 @@ public class TypeChecker
private void TypeCheckWhile(WhileNode whileNode) private void TypeCheckWhile(WhileNode whileNode)
{ {
var conditionType = TypeCheckExpression(whileNode.Condition); var conditionType = TypeCheckExpression(whileNode.Condition, NubPrimitiveType.Bool);
if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool)) if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
{ {
ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition); ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition);
@@ -337,7 +329,7 @@ public class TypeChecker
if (returnNode.Value.HasValue) if (returnNode.Value.HasValue)
{ {
var returnType = TypeCheckExpression(returnNode.Value.Value); var returnType = TypeCheckExpression(returnNode.Value.Value, _currentFunctionReturnType);
if (returnType == null) return; if (returnType == null) return;
if (_currentFunctionReturnType == null) if (_currentFunctionReturnType == null)
@@ -361,7 +353,7 @@ public class TypeChecker
{ {
var dereferenceType = TypeCheckExpression(dereferenceAssignment.Dereference); var dereferenceType = TypeCheckExpression(dereferenceAssignment.Dereference);
if (dereferenceType == null) return; if (dereferenceType == null) return;
var valueType = TypeCheckExpression(dereferenceAssignment.Value); var valueType = TypeCheckExpression(dereferenceAssignment.Value, dereferenceType);
if (valueType == null) return; if (valueType == null) return;
if (!NubType.IsCompatibleWith(dereferenceType, valueType)) if (!NubType.IsCompatibleWith(dereferenceType, valueType))
@@ -370,17 +362,17 @@ public class TypeChecker
} }
} }
private NubType? TypeCheckExpression(ExpressionNode expression) private NubType? TypeCheckExpression(ExpressionNode expression, NubType? expectedType = null)
{ {
var resultType = expression switch var resultType = expression switch
{ {
AddressOfNode addressOf => TypeCheckAddressOf(addressOf), AddressOfNode addressOf => TypeCheckAddressOf(addressOf),
ArrayIndexAccessNode arrayIndex => TypeCheckArrayIndex(arrayIndex), ArrayIndexAccessNode arrayIndex => TypeCheckArrayIndex(arrayIndex),
ArrayInitializerNode arrayInitializer => TypeCheckArrayInitializer(arrayInitializer), ArrayInitializerNode arrayInitializer => TypeCheckArrayInitializer(arrayInitializer),
LiteralNode literal => TypeCheckLiteral(literal), LiteralNode literal => TypeCheckLiteral(literal, expectedType),
IdentifierNode identifier => TypeCheckIdentifier(identifier), IdentifierNode identifier => TypeCheckIdentifier(identifier),
BinaryExpressionNode binaryExpr => TypeCheckBinaryExpression(binaryExpr), BinaryExpressionNode binaryExpr => TypeCheckBinaryExpression(binaryExpr),
CastNode cast => TypeCheckCast(cast), // CastNode cast => TypeCheckCast(cast),
DereferenceNode dereference => TypeCheckDereference(dereference), DereferenceNode dereference => TypeCheckDereference(dereference),
FixedArrayInitializerNode fixedArray => TypeCheckFixedInitializerArray(fixedArray), FixedArrayInitializerNode fixedArray => TypeCheckFixedInitializerArray(fixedArray),
FuncCallNode funcCallExpr => TypeCheckFuncCall(funcCallExpr, funcCallExpr), FuncCallNode funcCallExpr => TypeCheckFuncCall(funcCallExpr, funcCallExpr),
@@ -398,15 +390,34 @@ public class TypeChecker
return resultType; return resultType;
} }
private NubType TypeCheckLiteral(LiteralNode literal) private NubType? TypeCheckLiteral(LiteralNode literal, NubType? expectedType = null)
{ {
if (expectedType != null)
{
if (expectedType.IsNumber && literal.Kind is not LiteralKind.Integer and not LiteralKind.Float)
{
ReportError("Expression expects a numeric literal", literal);
return null;
}
if (expectedType.IsInteger && literal.Kind == LiteralKind.Float)
{
if (literal.Kind == LiteralKind.Float)
{
ReportWarning("Possible loss of precision when using float in integer context", literal);
}
}
return expectedType;
}
return literal.Kind switch return literal.Kind switch
{ {
LiteralKind.Integer => NubPrimitiveType.I64, LiteralKind.Integer => NubPrimitiveType.I64,
LiteralKind.Float => NubPrimitiveType.F64, LiteralKind.Float => NubPrimitiveType.F64,
LiteralKind.String => NubPrimitiveType.String, LiteralKind.String => NubPrimitiveType.String,
LiteralKind.Bool => NubPrimitiveType.Bool, LiteralKind.Bool => NubPrimitiveType.Bool,
_ => throw new UnreachableException() _ => throw new ArgumentOutOfRangeException()
}; };
} }
@@ -414,10 +425,10 @@ public class TypeChecker
{ {
var expressionType = TypeCheckExpression(arrayIndexAccess.Array); var expressionType = TypeCheckExpression(arrayIndexAccess.Array);
if (expressionType == null) return null; if (expressionType == null) return null;
var indexType = TypeCheckExpression(arrayIndexAccess.Index); var indexType = TypeCheckExpression(arrayIndexAccess.Index, NubPrimitiveType.U64);
if (indexType != null && !IsInteger(indexType)) if (indexType is { IsInteger: false })
{ {
ReportError("Array index type must be an integer", arrayIndexAccess.Index); ReportError("Array index type must be a number", arrayIndexAccess.Index);
} }
if (expressionType is NubArrayType arrayType) if (expressionType is NubArrayType arrayType)
@@ -436,8 +447,8 @@ public class TypeChecker
private NubType TypeCheckArrayInitializer(ArrayInitializerNode arrayInitializer) private NubType TypeCheckArrayInitializer(ArrayInitializerNode arrayInitializer)
{ {
var capacityType = TypeCheckExpression(arrayInitializer.Capacity); var capacityType = TypeCheckExpression(arrayInitializer.Capacity, NubPrimitiveType.U64);
if (capacityType != null && !IsInteger(capacityType)) if (capacityType is { IsInteger: false })
{ {
ReportError("Array capacity type must be an integer", arrayInitializer.Capacity); ReportError("Array capacity type must be an integer", arrayInitializer.Capacity);
} }
@@ -516,12 +527,12 @@ public class TypeChecker
} }
} }
private NubType? TypeCheckCast(CastNode cast) // private NubType? TypeCheckCast(CastNode cast)
{ // {
TypeCheckExpression(cast.Expression); // TypeCheckExpression(cast.Expression, cast.TargetType);
// TODO: Check if castable // // TODO: Check if castable
return cast.TargetType; // return cast.TargetType;
} // }
private NubType? TypeCheckStructInitializer(StructInitializerNode structInit) private NubType? TypeCheckStructInitializer(StructInitializerNode structInit)
{ {
@@ -543,7 +554,7 @@ public class TypeChecker
continue; continue;
} }
var initializerType = TypeCheckExpression(initializer.Value); var initializerType = TypeCheckExpression(initializer.Value, definitionField.Type);
if (initializerType != null && !NubType.IsCompatibleWith(initializerType, definitionField.Type)) if (initializerType != null && !NubType.IsCompatibleWith(initializerType, definitionField.Type))
{ {
ReportError($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'", initializer.Value); ReportError($"Cannot initialize field '{initializer.Key}' of type '{definitionField.Type}' with expression of type '{initializerType}'", initializer.Value);
@@ -654,6 +665,12 @@ public class TypeChecker
_diagnostics.Add(diagnostic); _diagnostics.Add(diagnostic);
} }
private void ReportWarning(string message, Node node)
{
var diagnostic = Diagnostic.Warning(message).At(node).Build();
_diagnostics.Add(diagnostic);
}
private static bool IsNumeric(NubType type) private static bool IsNumeric(NubType type)
{ {
if (type is not NubPrimitiveType primitiveType) if (type is not NubPrimitiveType primitiveType)
@@ -679,36 +696,13 @@ public class TypeChecker
} }
} }
private static bool IsInteger(NubType type) private IFuncSignature? LookupFuncSignature(string @namespace, string name)
{
if (type is not NubPrimitiveType primitiveType)
{
return false;
}
switch (primitiveType.Kind)
{
case PrimitiveTypeKind.I8:
case PrimitiveTypeKind.I16:
case PrimitiveTypeKind.I32:
case PrimitiveTypeKind.I64:
case PrimitiveTypeKind.U8:
case PrimitiveTypeKind.U16:
case PrimitiveTypeKind.U32:
case PrimitiveTypeKind.U64:
return true;
default:
return false;
}
}
private IFuncSignature? LookupFuncSignature(string @namespace, string name, List<NubType> parameters)
{ {
return _sourceFiles return _sourceFiles
.Where(f => f.Namespace == @namespace) .Where(f => f.Namespace == @namespace)
.SelectMany(f => f.Definitions) .SelectMany(f => f.Definitions)
.OfType<IFuncSignature>() .OfType<IFuncSignature>()
.FirstOrDefault(f => f.SignatureMatches(name, parameters)); .FirstOrDefault(f => f.Name == name);
} }
private StructDefinitionNode? LookupStructDefinition(string @namespace, string name) private StructDefinitionNode? LookupStructDefinition(string @namespace, string name)
@@ -717,6 +711,6 @@ public class TypeChecker
.Where(f => f.Namespace == @namespace) .Where(f => f.Namespace == @namespace)
.SelectMany(f => f.Definitions) .SelectMany(f => f.Definitions)
.OfType<StructDefinitionNode>() .OfType<StructDefinitionNode>()
.FirstOrDefault(d => d.Name == name); .SingleOrDefault(d => d.Name == name);
} }
} }