Add literal conversion based on context at compile time

This commit is contained in:
nub31
2025-06-07 20:34:10 +02:00
parent 23f955f8b2
commit 8d6f2f925a
10 changed files with 618 additions and 600 deletions

View File

@@ -1,5 +1,17 @@
namespace main
struct Human {
age: ^u64
}
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;
Next();
}
else if (next == 'f')
{
isFloat = true;
Next();
break;
}
else
{
break;

View File

@@ -20,37 +20,6 @@ public interface IFuncSignature
public List<FuncParameter> Parameters { 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 : "")}";
}

View File

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

View File

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

View File

@@ -24,6 +24,30 @@ public abstract class NubType
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 int GetHashCode();
public abstract override string ToString();

View File

@@ -71,7 +71,7 @@ public class TypeChecker
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))
{
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);
if (memberType == null) return;
var valueType = TypeCheckExpression(memberAssignment.Value);
var valueType = TypeCheckExpression(memberAssignment.Value, memberType);
if (valueType == null) return;
if (!NubType.IsCompatibleWith(memberType, valueType))
@@ -166,7 +166,7 @@ public class TypeChecker
{
var itemType = TypeCheckExpression(arrayIndexAssignment.ArrayIndexAccess);
if (itemType == null) return;
var valueType = TypeCheckExpression(arrayIndexAssignment.Value);
var valueType = TypeCheckExpression(arrayIndexAssignment.Value, itemType);
if (valueType == null) return;
if (!NubType.IsCompatibleWith(itemType, valueType))
@@ -177,19 +177,18 @@ public class TypeChecker
private void TypeCheckVariableAssignment(VariableAssignmentNode variableAssignment)
{
var valueType = TypeCheckExpression(variableAssignment.Value);
if (valueType == null) return;
if (!_variables.TryGetValue(variableAssignment.Identifier.Identifier, out var existingVariable))
if (!_variables.TryGetValue(variableAssignment.Identifier.Identifier, out var variable))
{
ReportError($"Variable '{variableAssignment.Identifier}' is not declared", variableAssignment);
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}'",
variableAssignment);
ReportError($"Cannot assign expression of type '{variableAssignment.Value.Type}' to variable '{variableAssignment.Identifier}' with type '{variable}'", variableAssignment);
}
}
@@ -197,14 +196,14 @@ public class TypeChecker
{
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)
{
var valueType = TypeCheckExpression(variableDeclaration.Value.Value);
var valueType = TypeCheckExpression(variableDeclaration.Value.Value, variableDeclaration.ExplicitType.Value);
if (valueType == null) return;
type = valueType;
}
@@ -254,31 +253,21 @@ public class TypeChecker
private NubType? TypeCheckFuncCall(FuncCallNode funcCall, Node node)
{
List<NubType> parameterTypes = [];
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);
var funcDefinition = LookupFuncSignature(funcCall.Namespace, funcCall.Name);
if (funcDefinition == null)
{
ReportError($"Function '{funcCall.Name}' is not defined", node);
ReportError($"Function '{funcCall}' is not defined", node);
return null;
}
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;
}
for (var i = 0; i < funcCall.Parameters.Count; i++)
{
var argType = funcCall.Parameters[i].Type;
NubType paramType;
if (i < funcDefinition.Parameters.Count)
{
@@ -290,13 +279,16 @@ public class TypeChecker
}
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;
}
var argType = TypeCheckExpression(funcCall.Parameters[i], paramType);
if (argType == null) return null;
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)
{
var conditionType = TypeCheckExpression(ifNode.Condition);
var conditionType = TypeCheckExpression(ifNode.Condition, NubPrimitiveType.Bool);
if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
{
ReportError($"If condition must be a boolean expression, got '{conditionType}'", ifNode.Condition);
@@ -322,7 +314,7 @@ public class TypeChecker
private void TypeCheckWhile(WhileNode whileNode)
{
var conditionType = TypeCheckExpression(whileNode.Condition);
var conditionType = TypeCheckExpression(whileNode.Condition, NubPrimitiveType.Bool);
if (conditionType != null && !conditionType.Equals(NubPrimitiveType.Bool))
{
ReportError($"While condition must be a boolean expression, got '{conditionType}'", whileNode.Condition);
@@ -337,7 +329,7 @@ public class TypeChecker
if (returnNode.Value.HasValue)
{
var returnType = TypeCheckExpression(returnNode.Value.Value);
var returnType = TypeCheckExpression(returnNode.Value.Value, _currentFunctionReturnType);
if (returnType == null) return;
if (_currentFunctionReturnType == null)
@@ -361,7 +353,7 @@ public class TypeChecker
{
var dereferenceType = TypeCheckExpression(dereferenceAssignment.Dereference);
if (dereferenceType == null) return;
var valueType = TypeCheckExpression(dereferenceAssignment.Value);
var valueType = TypeCheckExpression(dereferenceAssignment.Value, dereferenceType);
if (valueType == null) return;
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
{
AddressOfNode addressOf => TypeCheckAddressOf(addressOf),
ArrayIndexAccessNode arrayIndex => TypeCheckArrayIndex(arrayIndex),
ArrayInitializerNode arrayInitializer => TypeCheckArrayInitializer(arrayInitializer),
LiteralNode literal => TypeCheckLiteral(literal),
LiteralNode literal => TypeCheckLiteral(literal, expectedType),
IdentifierNode identifier => TypeCheckIdentifier(identifier),
BinaryExpressionNode binaryExpr => TypeCheckBinaryExpression(binaryExpr),
CastNode cast => TypeCheckCast(cast),
// CastNode cast => TypeCheckCast(cast),
DereferenceNode dereference => TypeCheckDereference(dereference),
FixedArrayInitializerNode fixedArray => TypeCheckFixedInitializerArray(fixedArray),
FuncCallNode funcCallExpr => TypeCheckFuncCall(funcCallExpr, funcCallExpr),
@@ -398,15 +390,34 @@ public class TypeChecker
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
{
LiteralKind.Integer => NubPrimitiveType.I64,
LiteralKind.Float => NubPrimitiveType.F64,
LiteralKind.String => NubPrimitiveType.String,
LiteralKind.Bool => NubPrimitiveType.Bool,
_ => throw new UnreachableException()
_ => throw new ArgumentOutOfRangeException()
};
}
@@ -414,10 +425,10 @@ public class TypeChecker
{
var expressionType = TypeCheckExpression(arrayIndexAccess.Array);
if (expressionType == null) return null;
var indexType = TypeCheckExpression(arrayIndexAccess.Index);
if (indexType != null && !IsInteger(indexType))
var indexType = TypeCheckExpression(arrayIndexAccess.Index, NubPrimitiveType.U64);
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)
@@ -436,8 +447,8 @@ public class TypeChecker
private NubType TypeCheckArrayInitializer(ArrayInitializerNode arrayInitializer)
{
var capacityType = TypeCheckExpression(arrayInitializer.Capacity);
if (capacityType != null && !IsInteger(capacityType))
var capacityType = TypeCheckExpression(arrayInitializer.Capacity, NubPrimitiveType.U64);
if (capacityType is { IsInteger: false })
{
ReportError("Array capacity type must be an integer", arrayInitializer.Capacity);
}
@@ -516,12 +527,12 @@ public class TypeChecker
}
}
private NubType? TypeCheckCast(CastNode cast)
{
TypeCheckExpression(cast.Expression);
// TODO: Check if castable
return cast.TargetType;
}
// private NubType? TypeCheckCast(CastNode cast)
// {
// TypeCheckExpression(cast.Expression, cast.TargetType);
// // TODO: Check if castable
// return cast.TargetType;
// }
private NubType? TypeCheckStructInitializer(StructInitializerNode structInit)
{
@@ -543,7 +554,7 @@ public class TypeChecker
continue;
}
var initializerType = TypeCheckExpression(initializer.Value);
var initializerType = TypeCheckExpression(initializer.Value, 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);
@@ -654,6 +665,12 @@ public class TypeChecker
_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)
{
if (type is not NubPrimitiveType primitiveType)
@@ -679,36 +696,13 @@ public class TypeChecker
}
}
private static bool IsInteger(NubType type)
{
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)
private IFuncSignature? LookupFuncSignature(string @namespace, string name)
{
return _sourceFiles
.Where(f => f.Namespace == @namespace)
.SelectMany(f => f.Definitions)
.OfType<IFuncSignature>()
.FirstOrDefault(f => f.SignatureMatches(name, parameters));
.FirstOrDefault(f => f.Name == name);
}
private StructDefinitionNode? LookupStructDefinition(string @namespace, string name)
@@ -717,6 +711,6 @@ public class TypeChecker
.Where(f => f.Namespace == @namespace)
.SelectMany(f => f.Definitions)
.OfType<StructDefinitionNode>()
.FirstOrDefault(d => d.Name == name);
.SingleOrDefault(d => d.Name == name);
}
}