...
This commit is contained in:
@@ -76,4 +76,6 @@ public record DereferenceNode(TypeNode Type, ExpressionNode Expression) : LValue
|
||||
|
||||
public record ConvertIntNode(TypeNode Type, ExpressionNode Value, IntTypeNode ValueType, IntTypeNode TargetType) : RValueExpressionNode(Type);
|
||||
|
||||
public record ConvertFloatNode(TypeNode Type, ExpressionNode Value, FloatTypeNode ValueType, FloatTypeNode TargetType) : RValueExpressionNode(Type);
|
||||
public record ConvertFloatNode(TypeNode Type, ExpressionNode Value, FloatTypeNode ValueType, FloatTypeNode TargetType) : RValueExpressionNode(Type);
|
||||
|
||||
public record SizeCompilerMacroNode(TypeNode Type, TypeNode TargetType) : RValueExpressionNode(Type);
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace NubLang.TypeChecking.Node;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace NubLang.TypeChecking.Node;
|
||||
|
||||
public abstract class TypeNode : IEquatable<TypeNode>
|
||||
{
|
||||
@@ -156,4 +159,35 @@ public class StringTypeNode : TypeNode
|
||||
public override string ToString() => "string";
|
||||
public override bool Equals(TypeNode? other) => other is StringTypeNode;
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(StringTypeNode));
|
||||
}
|
||||
|
||||
public static class NameMangler
|
||||
{
|
||||
public static string Mangle(params IEnumerable<TypeNode> types)
|
||||
{
|
||||
var readable = string.Join("_", types.Select(EncodeType));
|
||||
return ComputeShortHash(readable);
|
||||
}
|
||||
|
||||
private static string EncodeType(TypeNode node) => node switch
|
||||
{
|
||||
VoidTypeNode => "V",
|
||||
BoolTypeNode => "B",
|
||||
IntTypeNode i => (i.Signed ? "I" : "U") + i.Width,
|
||||
FloatTypeNode f => "F" + f.Width,
|
||||
CStringTypeNode => "CS",
|
||||
StringTypeNode => "S",
|
||||
PointerTypeNode p => "P" + EncodeType(p.BaseType),
|
||||
ArrayTypeNode a => "A" + EncodeType(a.ElementType),
|
||||
FuncTypeNode fn => "FN(" + string.Join(",", fn.Parameters.Select(EncodeType)) + ")" + EncodeType(fn.ReturnType),
|
||||
StructTypeNode st => "ST(" + st.Module + "." + st.Name + ")",
|
||||
_ => throw new NotSupportedException($"Cannot encode type: {node}")
|
||||
};
|
||||
|
||||
private static string ComputeShortHash(string input)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(input);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
return Convert.ToHexString(hash[..8]).ToLower();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using NubLang.Diagnostics;
|
||||
using NubLang.Modules;
|
||||
using NubLang.Parsing.Syntax;
|
||||
@@ -15,6 +16,8 @@ public sealed class TypeChecker
|
||||
|
||||
private readonly Stack<Scope> _scopes = [];
|
||||
private readonly Stack<TypeNode> _funcReturnTypes = [];
|
||||
private readonly Dictionary<(string Module, string Name), TypeNode> _typeCache = new();
|
||||
private readonly HashSet<(string Module, string Name)> _resolvingTypes = [];
|
||||
|
||||
private Scope Scope => _scopes.Peek();
|
||||
|
||||
@@ -38,12 +41,26 @@ public sealed class TypeChecker
|
||||
Diagnostics.Clear();
|
||||
Definitions.Clear();
|
||||
ReferencedStructTypes.Clear();
|
||||
_typeCache.Clear();
|
||||
_resolvingTypes.Clear();
|
||||
|
||||
foreach (var definition in _syntaxTree.Definitions)
|
||||
{
|
||||
try
|
||||
{
|
||||
Definitions.Add(CheckDefinition(definition));
|
||||
switch (definition)
|
||||
{
|
||||
case FuncSyntax funcSyntax:
|
||||
Definitions.Add(CheckFuncDefinition(funcSyntax));
|
||||
break;
|
||||
case StructSyntax structSyntax:
|
||||
Definitions.Add(CheckStructDefinition(structSyntax));
|
||||
break;
|
||||
case StructTemplateSyntax:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(definition));
|
||||
}
|
||||
}
|
||||
catch (TypeCheckerException e)
|
||||
{
|
||||
@@ -52,16 +69,6 @@ public sealed class TypeChecker
|
||||
}
|
||||
}
|
||||
|
||||
private DefinitionNode CheckDefinition(DefinitionSyntax node)
|
||||
{
|
||||
return node switch
|
||||
{
|
||||
FuncSyntax definition => CheckFuncDefinition(definition),
|
||||
StructSyntax definition => CheckStructDefinition(definition),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
||||
};
|
||||
}
|
||||
|
||||
private StructNode CheckStructDefinition(StructSyntax node)
|
||||
{
|
||||
var fieldTypes = node.Fields
|
||||
@@ -77,45 +84,45 @@ public sealed class TypeChecker
|
||||
}
|
||||
|
||||
var type = new StructTypeNode(_syntaxTree.Metadata.ModuleName, node.Name, fieldTypes, functionTypes);
|
||||
|
||||
var fields = new List<StructFieldNode>();
|
||||
foreach (var field in node.Fields)
|
||||
{
|
||||
var value = Optional.Empty<ExpressionNode>();
|
||||
if (field.Value.HasValue)
|
||||
{
|
||||
value = CheckExpression(field.Value.Value, ResolveType(field.Type));
|
||||
}
|
||||
|
||||
fields.Add(new StructFieldNode(field.Name, ResolveType(field.Type), value));
|
||||
}
|
||||
|
||||
var functions = new List<StructFuncNode>();
|
||||
foreach (var function in node.Functions)
|
||||
{
|
||||
var scope = new Scope();
|
||||
scope.Declare(new Identifier("this", type, IdentifierKind.FunctionParameter));
|
||||
|
||||
foreach (var parameter in function.Signature.Parameters)
|
||||
{
|
||||
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
|
||||
}
|
||||
|
||||
_funcReturnTypes.Push(ResolveType(function.Signature.ReturnType));
|
||||
var body = CheckBlock(function.Body, scope);
|
||||
_funcReturnTypes.Pop();
|
||||
functions.Add(new StructFuncNode(function.Name, CheckFuncSignature(function.Signature), body));
|
||||
}
|
||||
var fields = node.Fields.Select(CheckStructField).ToList();
|
||||
var functions = node.Functions.Select(x => CheckStructFunc(type, x)).ToList();
|
||||
|
||||
return new StructNode(type, _syntaxTree.Metadata.ModuleName, node.Name, fields, functions);
|
||||
}
|
||||
|
||||
private StructFuncNode CheckStructFunc(StructTypeNode type, StructFuncSyntax function, Scope? scope = null)
|
||||
{
|
||||
scope ??= new Scope();
|
||||
scope.DeclareVariable(new Variable("this", type, VariableKind.FunctionParameter));
|
||||
|
||||
foreach (var parameter in function.Signature.Parameters)
|
||||
{
|
||||
scope.DeclareVariable(new Variable(parameter.Name, ResolveType(parameter.Type), VariableKind.FunctionParameter));
|
||||
}
|
||||
|
||||
_funcReturnTypes.Push(ResolveType(function.Signature.ReturnType));
|
||||
var body = CheckBlock(function.Body, scope);
|
||||
_funcReturnTypes.Pop();
|
||||
return new StructFuncNode(function.Name, CheckFuncSignature(function.Signature), body);
|
||||
}
|
||||
|
||||
private StructFieldNode CheckStructField(StructFieldSyntax field)
|
||||
{
|
||||
var value = Optional.Empty<ExpressionNode>();
|
||||
if (field.Value.HasValue)
|
||||
{
|
||||
value = CheckExpression(field.Value.Value, ResolveType(field.Type));
|
||||
}
|
||||
|
||||
return new StructFieldNode(field.Name, ResolveType(field.Type), value);
|
||||
}
|
||||
|
||||
private FuncNode CheckFuncDefinition(FuncSyntax node)
|
||||
{
|
||||
var scope = new Scope();
|
||||
foreach (var parameter in node.Signature.Parameters)
|
||||
{
|
||||
scope.Declare(new Identifier(parameter.Name, ResolveType(parameter.Type), IdentifierKind.FunctionParameter));
|
||||
scope.DeclareVariable(new Variable(parameter.Name, ResolveType(parameter.Type), VariableKind.FunctionParameter));
|
||||
}
|
||||
|
||||
var signature = CheckFuncSignature(node.Signature);
|
||||
@@ -236,7 +243,7 @@ public sealed class TypeChecker
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Cannot infer type of variable {statement.Name}").At(statement).Build());
|
||||
}
|
||||
|
||||
Scope.Declare(new Identifier(statement.Name, type, IdentifierKind.Variable));
|
||||
Scope.DeclareVariable(new Variable(statement.Name, type, VariableKind.Variable));
|
||||
|
||||
return new VariableDeclarationNode(statement.Name, Optional.OfNullable(assignmentNode), type);
|
||||
}
|
||||
@@ -262,10 +269,10 @@ public sealed class TypeChecker
|
||||
case ArrayTypeNode arrayType:
|
||||
{
|
||||
var scope = Scope.SubScope();
|
||||
scope.Declare(new Identifier(statement.ElementIdent, arrayType.ElementType, IdentifierKind.FunctionParameter));
|
||||
scope.DeclareVariable(new Variable(statement.ElementIdent, arrayType.ElementType, VariableKind.FunctionParameter));
|
||||
if (statement.IndexIdent != null)
|
||||
{
|
||||
scope.Declare(new Identifier(statement.ElementIdent, new IntTypeNode(true, 64), IdentifierKind.FunctionParameter));
|
||||
scope.DeclareVariable(new Variable(statement.ElementIdent, new IntTypeNode(true, 64), VariableKind.FunctionParameter));
|
||||
}
|
||||
|
||||
var body = CheckBlock(statement.Body, scope);
|
||||
@@ -273,7 +280,7 @@ public sealed class TypeChecker
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Type {target.Type} is not an iterable target").Build());
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Type {target.Type} is not an iterable target").At(statement.Target).Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,6 +313,8 @@ public sealed class TypeChecker
|
||||
LiteralSyntax expression => CheckLiteral(expression, expectedType),
|
||||
StructFieldAccessSyntax expression => CheckStructFieldAccess(expression),
|
||||
StructInitializerSyntax expression => CheckStructInitializer(expression, expectedType),
|
||||
InterpretCompilerMacroSyntax expression => CheckExpression(expression.Target) with { Type = ResolveType(expression.Type) },
|
||||
SizeCompilerMacroSyntax expression => new SizeCompilerMacroNode(new IntTypeNode(false, 64), ResolveType(expression.Type)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
||||
};
|
||||
|
||||
@@ -338,7 +347,7 @@ public sealed class TypeChecker
|
||||
var target = CheckExpression(expression.Target);
|
||||
if (target is not LValueExpressionNode lvalue)
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error("Cannot take address of an rvalue").Build());
|
||||
throw new TypeCheckerException(Diagnostic.Error("Cannot take address of an rvalue").At(expression).Build());
|
||||
}
|
||||
|
||||
var type = new PointerTypeNode(target.Type);
|
||||
@@ -469,7 +478,7 @@ public sealed class TypeChecker
|
||||
var operand = CheckExpression(expression.Operand);
|
||||
if (operand.Type is not IntTypeNode { Signed: false } or FloatTypeNode)
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error("Negation operator must be used with signed integer or float types").Build());
|
||||
throw new TypeCheckerException(Diagnostic.Error("Negation operator must be used with signed integer or float types").At(expression).Build());
|
||||
}
|
||||
|
||||
return new UnaryExpressionNode(operand.Type, UnaryOperator.Negate, operand);
|
||||
@@ -479,7 +488,7 @@ public sealed class TypeChecker
|
||||
var operand = CheckExpression(expression.Operand);
|
||||
if (operand.Type is not BoolTypeNode)
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error("Invert operator must be used with booleans").Build());
|
||||
throw new TypeCheckerException(Diagnostic.Error("Invert operator must be used with booleans").At(expression).Build());
|
||||
}
|
||||
|
||||
return new UnaryExpressionNode(operand.Type, UnaryOperator.Invert, operand);
|
||||
@@ -580,22 +589,25 @@ public sealed class TypeChecker
|
||||
return new StructFuncCallNode(function.Type.ReturnType, expression.Name, structType, target, parameters);
|
||||
}
|
||||
|
||||
throw new TypeCheckerException(Diagnostic.Error($"No function {expression.Name} exists on type {target.Type}").Build());
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error($"No function {expression.Name} exists on type {target.Type}")
|
||||
.At(expression)
|
||||
.Build());
|
||||
}
|
||||
|
||||
private ExpressionNode CheckLocalIdentifier(LocalIdentifierSyntax expression)
|
||||
{
|
||||
// First, look in the current scope for a matching identifier
|
||||
var scopeIdent = Scope.Lookup(expression.Name);
|
||||
var scopeIdent = Scope.LookupVariable(expression.Name);
|
||||
if (scopeIdent != null)
|
||||
{
|
||||
switch (scopeIdent.Kind)
|
||||
{
|
||||
case IdentifierKind.Variable:
|
||||
case VariableKind.Variable:
|
||||
{
|
||||
return new VariableIdentifierNode(scopeIdent.Type, expression.Name);
|
||||
}
|
||||
case IdentifierKind.FunctionParameter:
|
||||
case VariableKind.FunctionParameter:
|
||||
{
|
||||
return new FuncParameterIdentifierNode(scopeIdent.Type, expression.Name);
|
||||
}
|
||||
@@ -624,7 +636,11 @@ public sealed class TypeChecker
|
||||
{
|
||||
if (!_visibleModules.TryGetValue(expression.Module, out var module))
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Module {expression.Module} not found").WithHelp($"import \"{expression.Module}\"").At(expression).Build());
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error($"Module {expression.Module} not found")
|
||||
.WithHelp($"import \"{expression.Module}\"")
|
||||
.At(expression)
|
||||
.Build());
|
||||
}
|
||||
|
||||
var includePrivate = expression.Module == _syntaxTree.Metadata.ModuleName;
|
||||
@@ -638,7 +654,10 @@ public sealed class TypeChecker
|
||||
return new FuncIdentifierNode(type, expression.Module, expression.Name, function.ExternSymbol);
|
||||
}
|
||||
|
||||
throw new TypeCheckerException(Diagnostic.Error($"No exported symbol {expression.Name} not found in module {expression.Module}").At(expression).Build());
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error($"No exported symbol {expression.Name} not found in module {expression.Module}")
|
||||
.At(expression)
|
||||
.Build());
|
||||
}
|
||||
|
||||
private ExpressionNode CheckLiteral(LiteralSyntax expression, TypeNode? expectedType)
|
||||
@@ -825,16 +844,74 @@ public sealed class TypeChecker
|
||||
ArrayTypeSyntax arr => new ArrayTypeNode(ResolveType(arr.BaseType)),
|
||||
PointerTypeSyntax ptr => new PointerTypeNode(ResolveType(ptr.BaseType)),
|
||||
StringTypeSyntax => new StringTypeNode(),
|
||||
TemplateTypeSyntax template => ResolveTemplateType(template),
|
||||
VoidTypeSyntax => new VoidTypeNode(),
|
||||
_ => throw new NotSupportedException($"Unknown type syntax: {type}")
|
||||
};
|
||||
}
|
||||
|
||||
private readonly Dictionary<(string Module, string Name), TypeNode> _typeCache = new();
|
||||
private readonly HashSet<(string Module, string Name)> _resolvingTypes = [];
|
||||
private StructTypeNode ResolveTemplateType(TemplateTypeSyntax template)
|
||||
{
|
||||
// todo(nub31): Add module support for template types
|
||||
var definition = _syntaxTree.Definitions
|
||||
.OfType<StructTemplateSyntax>()
|
||||
.FirstOrDefault(x => x.Name == template.Name);
|
||||
|
||||
if (definition == null)
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Template {template.Name} does not exist").At(template).Build());
|
||||
}
|
||||
|
||||
if (definition.TemplateArguments.Count != template.TemplateParameters.Count)
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error($"Template {template.Name} has {definition.TemplateArguments.Count} arguments, but usage only has {template.TemplateParameters.Count} parameters")
|
||||
.At(template)
|
||||
.Build());
|
||||
}
|
||||
|
||||
var scope = new Scope();
|
||||
|
||||
for (var i = 0; i < definition.TemplateArguments.Count; i++)
|
||||
{
|
||||
scope.DeclareGenericType(definition.TemplateArguments[i], ResolveType(template.TemplateParameters[i]));
|
||||
}
|
||||
|
||||
_scopes.Push(scope);
|
||||
|
||||
var fields = definition.Fields
|
||||
.Select(x => new StructTypeField(x.Name, ResolveType(x.Type), x.Value.HasValue))
|
||||
.ToList();
|
||||
|
||||
var functions = definition.Functions
|
||||
.Select(x => new StructTypeFunc(x.Name, x.Hook, new FuncTypeNode(x.Signature.Parameters.Select(y => ResolveType(y.Type)).ToList(), ResolveType(x.Signature.ReturnType))))
|
||||
.ToList();
|
||||
|
||||
var name = $"{template.Name}.{NameMangler.Mangle(template.TemplateParameters.Select(ResolveType))}";
|
||||
|
||||
var type = new StructTypeNode(template.Module, name, fields, functions);
|
||||
|
||||
var checkedFields = definition.Fields.Select(CheckStructField).ToList();
|
||||
var checkedFunctions = definition.Functions.Select(x => CheckStructFunc(type, x, scope)).ToList();
|
||||
|
||||
Definitions.Add(new StructNode(type, template.Module, name, checkedFields, checkedFunctions));
|
||||
|
||||
_scopes.Pop();
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private TypeNode ResolveCustomType(CustomTypeSyntax customType)
|
||||
{
|
||||
if (_syntaxTree.Metadata.ModuleName == customType.Module && _scopes.TryPeek(out var scope))
|
||||
{
|
||||
var generic = scope.LookupGenericType(customType.Name);
|
||||
if (generic != null)
|
||||
{
|
||||
return generic;
|
||||
}
|
||||
}
|
||||
|
||||
var key = (customType.Module, customType.Name);
|
||||
|
||||
if (_typeCache.TryGetValue(key, out var cachedType))
|
||||
@@ -853,7 +930,11 @@ public sealed class TypeChecker
|
||||
{
|
||||
if (!_visibleModules.TryGetValue(customType.Module, out var module))
|
||||
{
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Module {customType.Module} not found").WithHelp($"import \"{customType.Module}\"").At(customType).Build());
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error($"Module {customType.Module} not found")
|
||||
.WithHelp($"import \"{customType.Module}\"")
|
||||
.At(customType)
|
||||
.Build());
|
||||
}
|
||||
|
||||
var includePrivate = customType.Module == _syntaxTree.Metadata.ModuleName;
|
||||
@@ -878,7 +959,10 @@ public sealed class TypeChecker
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new TypeCheckerException(Diagnostic.Error($"Type {customType.Name} not found in module {customType.Module}").At(customType).Build());
|
||||
throw new TypeCheckerException(Diagnostic
|
||||
.Error($"Type {customType.Name} not found in module {customType.Module}")
|
||||
.At(customType)
|
||||
.Build());
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -887,19 +971,20 @@ public sealed class TypeChecker
|
||||
}
|
||||
}
|
||||
|
||||
public enum IdentifierKind
|
||||
public enum VariableKind
|
||||
{
|
||||
Variable,
|
||||
FunctionParameter
|
||||
}
|
||||
|
||||
public record Identifier(string Name, TypeNode Type, IdentifierKind Kind);
|
||||
public record Variable(string Name, TypeNode Type, VariableKind Kind);
|
||||
|
||||
public class Scope(Scope? parent = null)
|
||||
{
|
||||
private readonly List<Identifier> _variables = [];
|
||||
private readonly List<Variable> _variables = [];
|
||||
private readonly Dictionary<string, TypeNode> _typeArguments = [];
|
||||
|
||||
public Identifier? Lookup(string name)
|
||||
public Variable? LookupVariable(string name)
|
||||
{
|
||||
var variable = _variables.FirstOrDefault(x => x.Name == name);
|
||||
if (variable != null)
|
||||
@@ -907,12 +992,22 @@ public class Scope(Scope? parent = null)
|
||||
return variable;
|
||||
}
|
||||
|
||||
return parent?.Lookup(name);
|
||||
return parent?.LookupVariable(name);
|
||||
}
|
||||
|
||||
public void Declare(Identifier identifier)
|
||||
public void DeclareVariable(Variable variable)
|
||||
{
|
||||
_variables.Add(identifier);
|
||||
_variables.Add(variable);
|
||||
}
|
||||
|
||||
public void DeclareGenericType(string typeArgument, TypeNode type)
|
||||
{
|
||||
_typeArguments[typeArgument] = type;
|
||||
}
|
||||
|
||||
public TypeNode? LookupGenericType(string typeArgument)
|
||||
{
|
||||
return _typeArguments.GetValueOrDefault(typeArgument);
|
||||
}
|
||||
|
||||
public Scope SubScope()
|
||||
|
||||
Reference in New Issue
Block a user