From 0be4e3562811c76dfb67c93a16680b4af73a3d89 Mon Sep 17 00:00:00 2001 From: nub31 Date: Thu, 5 Mar 2026 21:59:24 +0100 Subject: [PATCH] Add check for duplicate local ident --- compiler/TypeChecker.cs | 104 ++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/compiler/TypeChecker.cs b/compiler/TypeChecker.cs index 384f709..932f398 100644 --- a/compiler/TypeChecker.cs +++ b/compiler/TypeChecker.cs @@ -24,7 +24,7 @@ public class TypeChecker private readonly NodeDefinitionFunc function; private NubType functionReturnType = null!; private readonly ModuleGraph moduleGraph; - private readonly Scope scope = new(); + private readonly Stack> scopes = new(); private TypedNodeDefinitionFunc? CheckFunction(out List diagnostics) { @@ -51,7 +51,7 @@ public class TypeChecker } } - using (scope.EnterScope()) + using (EnterScope()) { foreach (var parameter in function.Parameters) { @@ -60,6 +60,7 @@ public class TypeChecker try { parameterType = ResolveType(parameter.Type); + DeclareLocalIdentifier(parameter.Name, parameterType); } catch (CompileException e) { @@ -68,7 +69,6 @@ public class TypeChecker continue; } - scope.DeclareIdentifier(parameter.Name.Ident, parameterType); parameters.Add(new TypedNodeDefinitionFunc.Param(parameter.Tokens, parameter.Name, parameterType)); } @@ -114,7 +114,7 @@ public class TypeChecker private TypedNodeStatementBlock CheckStatementBlock(NodeStatementBlock statement) { - using (scope.EnterScope()) + using (EnterScope()) { var statements = statement.Statements.Select(CheckStatement).ToList(); return new TypedNodeStatementBlock(statement.Tokens, statements); @@ -137,8 +137,19 @@ public class TypeChecker if (!condition.Type.IsAssignableTo(NubTypeBool.Instance)) throw BasicError("Condition part of if statement must be a boolean", condition); - var thenBlock = CheckStatement(statement.ThenBlock); - var elseBlock = statement.ElseBlock == null ? null : CheckStatement(statement.ElseBlock); + TypedNodeStatement thenBlock; + + using (EnterScope()) + { + thenBlock = CheckStatement(statement.ThenBlock); + } + + TypedNodeStatement? elseBlock; + + using (EnterScope()) + { + elseBlock = statement.ElseBlock == null ? null : CheckStatement(statement.ElseBlock); + } return new TypedNodeStatementIf(statement.Tokens, condition, thenBlock, elseBlock); } @@ -175,7 +186,7 @@ public class TypeChecker type ??= value.Type; - scope.DeclareIdentifier(statement.Name.Ident, type); + DeclareLocalIdentifier(statement.Name, type); return new TypedNodeStatementVariableDeclaration(statement.Tokens, statement.Name, type, value); } @@ -186,9 +197,11 @@ public class TypeChecker if (!condition.Type.IsAssignableTo(NubTypeBool.Instance)) throw BasicError("Condition part of if statement must be a boolean", condition); - var body = CheckStatement(statement.Body); - - return new TypedNodeStatementWhile(statement.Tokens, condition, body); + using (EnterScope()) + { + var body = CheckStatement(statement.Body); + return new TypedNodeStatementWhile(statement.Tokens, condition, body); + } } private TypedNodeStatementMatch CheckStatementMatch(NodeStatementMatch statement) @@ -214,14 +227,14 @@ public class TypeChecker uncoveredCases.Remove(@case.Variant.Ident); - using (scope.EnterScope()) + using (EnterScope()) { if (@case.VariableName != null) { if (variant.Type is null) throw BasicError("Cannot capture variable for enum variant without type", @case.VariableName); - scope.DeclareIdentifier(@case.VariableName.Ident, variant.Type); + DeclareLocalIdentifier(@case.VariableName, variant.Type); } var body = CheckStatement(@case.Body); @@ -416,14 +429,14 @@ public class TypeChecker { if (expression.Sections.Count == 1) { - var name = expression.Sections[0].Ident; + var name = expression.Sections[0]; - var localType = scope.GetIdentifierType(name); + var localType = GetIdentifierType(name.Ident); if (localType is not null) - return new TypedNodeExpressionLocalIdent(expression.Tokens, localType, name); + return new TypedNodeExpressionLocalIdent(expression.Tokens, localType, name.Ident); - if (moduleGraph.TryResolveIdentifier(currentModule, name, true, out var ident)) - return new TypedNodeExpressionGlobalIdent(expression.Tokens, ident.Type, currentModule, name); + if (moduleGraph.TryResolveIdentifier(currentModule, name.Ident, true, out var ident)) + return new TypedNodeExpressionGlobalIdent(expression.Tokens, ident.Type, currentModule, name.Ident); } else if (expression.Sections.Count == 2) { @@ -755,45 +768,44 @@ public class TypeChecker return new CompileException(Diagnostic.Error(message).At(fileName, node).Build()); } - private sealed class Scope + public void DeclareLocalIdentifier(TokenIdent name, NubType type) { - private readonly Stack> scopes = new(); + var existing = GetIdentifierType(name.Ident); + if (existing is not null) + throw BasicError($"Local identifier '{name.Ident}' is already defined", name); - public IDisposable EnterScope() - { - scopes.Push([]); - return new ScopeGuard(this); - } + scopes.Peek().Add(name.Ident, type); + } - public void DeclareIdentifier(string name, NubType type) + public NubType? GetIdentifierType(string name) + { + foreach (var scope in scopes) { - scopes.Peek().Add(name, type); - } - - public NubType? GetIdentifierType(string name) - { - foreach (var scope in scopes) + if (scope.TryGetValue(name, out var type)) { - if (scope.TryGetValue(name, out var type)) - { - return type; - } + return type; } - - return null; } - private void ExitScope() - { - scopes.Pop(); - } + return null; + } - private sealed class ScopeGuard(Scope owner) : IDisposable + public IDisposable EnterScope() + { + scopes.Push([]); + return new ScopeGuard(this); + } + + private void ExitScope() + { + scopes.Pop(); + } + + private sealed class ScopeGuard(TypeChecker owner) : IDisposable + { + public void Dispose() { - public void Dispose() - { - owner.ExitScope(); - } + owner.ExitScope(); } } }