diff --git a/docs/architecture/semantics-bindings.md b/docs/architecture/semantics-bindings.md index 802d6c7c..bc13e4f3 100644 --- a/docs/architecture/semantics-bindings.md +++ b/docs/architecture/semantics-bindings.md @@ -451,7 +451,8 @@ are `types.go`, `syntax.go`, `relations.go`, `compatibility.go`, `lookup.go`, - Expected numeric types influence literal construction and identifier adaptation. - Numeric literals use default or explicit numeric types and target-aware parsing. - Boolean and string literals produce typed constant values. -- Constant identifiers must resolve to `SymbolConst`. +- Constant identifiers consume their resolver-bound `SymbolIndex` entry and must + denote `SymbolConst`; evaluation never repeats lexical name lookup. - Unary and binary operations delegate folding to `constvalue`. - Variant constructions use private typechecker evidence before THIR publication. - Non-copyable variant types are not evaluated as constants. diff --git a/internal/pipeline/pipeline_test.go b/internal/pipeline/pipeline_test.go index 90890248..32e397b4 100644 --- a/internal/pipeline/pipeline_test.go +++ b/internal/pipeline/pipeline_test.go @@ -202,8 +202,12 @@ func TestPipelineLowersSequenceIndexesAsUsizeAcrossTargets(t *testing.T) { Extension: peeper.SourceExt, TargetOS: "linux", TargetArch: test.arch, - }, "", `fn main() { + }, "", `const selected: i32 = 0; +fn main() { + const saved: i32 = selected; + const selected: i32 = 1; let items = [1]i32{1}; + let chosen = items[saved]; for index, value in items {} }`, func(entry *module.Module) { if entry.MIR == nil || len(entry.MIR.Funcs) != 1 { @@ -1147,55 +1151,52 @@ func TestPipelineFinalizesMissingReturnDiagnosticInCFGPhase(t *testing.T) { t.Fatalf("missing-return diagnostic unavailable at CFG phase:\n%s", diag.EmitAllToString()) } -func TestPipelineReportsConstantConditionInCFGPhase(t *testing.T) { - diag := diagnostics.NewDiagnosticBag() - const entryPath = "entry" + peeper.SourceExt - entry := parseModuleSource(entryPath, `fn main() -> i32 { - if false { - return 1; - } +func TestPipelineReportsConstantConditionsInCFGPhase(t *testing.T) { + for _, test := range []struct { + name, source, warning string + }{ + {"literal false", `fn main() -> i32 { + if false { return 1; } return 0; -}`, diag) - entry.Phase = phase.Parsed - ctx := project.NewWithConfig(project.Config{RootDir: ".", Extension: peeper.SourceExt}, diag) - ctx.AddModule(entry) - - for entry.Phase < phase.Typechecked { - if !advanceModulePhase(ctx, entry, diag) { - t.Fatalf("advanceModulePhase stopped at %v", entry.Phase) - } - } - for _, item := range diag.Diagnostics() { - if item != nil && item.Code == diagnostics.WarnConstantConditionFalse { - t.Fatal("constant-condition warning emitted before CFG phase") - } +}`, diagnostics.WarnConstantConditionFalse}, + {"local true", `fn main() { const Flag = true; if Flag { print(1); } }`, diagnostics.WarnConstantConditionTrue}, + {"local false", `fn main() { const Flag = false; if Flag { print(1); } }`, diagnostics.WarnConstantConditionFalse}, + {"const shadow true", `fn main() { + const value: i32 = 10; + { + const saved: i32 = value; + const value: i32 = 20; + if saved == 10 { print(1); } } - entry.AST = nil - if !advanceModulePhase(ctx, entry, diag) { - t.Fatal("CFG phase requires AST") +}`, diagnostics.WarnConstantConditionTrue}, + {"const shadow false", `fn main() { + const value: i32 = 10; + { + const saved: i32 = value; + const value: i32 = 20; + if saved == 20 { print(1); } } - if entry.MIR != nil { - t.Fatalf("CFG phase produced MIR: %#v", entry.MIR) +}`, diagnostics.WarnConstantConditionFalse}, + {"let shadow true", `fn main() { + const value: i32 = 10; + { + const saved: i32 = value; + let value: i32 = 20; + if saved == 10 { print(1); } } - for _, item := range diag.Diagnostics() { - if item != nil && item.Code == diagnostics.WarnConstantConditionFalse { - return - } +}`, diagnostics.WarnConstantConditionTrue}, + {"let shadow false", `fn main() { + const value: i32 = 10; + { + const saved: i32 = value; + let value: i32 = 20; + if saved == 20 { print(1); } } - t.Fatalf("constant-condition diagnostic unavailable at CFG phase:\n%s", diag.EmitAllToString()) -} - -func TestPipelinePublishesLocalConstantConditions(t *testing.T) { - for _, test := range []struct { - name, value string - warning string - }{ - {"true", "true", diagnostics.WarnConstantConditionTrue}, - {"false", "false", diagnostics.WarnConstantConditionFalse}, +}`, diagnostics.WarnConstantConditionFalse}, } { t.Run(test.name, func(t *testing.T) { diag := diagnostics.NewDiagnosticBag() - entry := parseModuleSource("entry"+peeper.SourceExt, "fn main() { const Flag = "+test.value+"; if Flag { print(1); } }", diag) + entry := parseModuleSource("entry"+peeper.SourceExt, test.source, diag) entry.Phase = phase.Parsed ctx := project.NewWithConfig(project.Config{RootDir: ".", Extension: peeper.SourceExt}, diag) ctx.AddModule(entry) @@ -1204,16 +1205,37 @@ func TestPipelinePublishesLocalConstantConditions(t *testing.T) { t.Fatalf("advanceModulePhase stopped at %v", entry.Phase) } } + if diag.HasErrors() { + t.Fatalf("unexpected diagnostics:\n%s", diag.EmitAllToString()) + } + for _, item := range diag.Diagnostics() { + if item != nil && (item.Code == diagnostics.WarnConstantConditionTrue || item.Code == diagnostics.WarnConstantConditionFalse) { + t.Fatal("constant-condition warning emitted before CFG phase") + } + } entry.AST = nil - if !advanceModulePhase(ctx, entry, diag) { - t.Fatal("CFG phase requires AST") + if !advanceModulePhase(ctx, entry, diag) || entry.Phase != phase.CFG { + t.Fatal("CFG phase did not consume THIR without AST") + } + if diag.HasErrors() { + t.Fatalf("unexpected CFG diagnostics:\n%s", diag.EmitAllToString()) + } + if entry.MIR != nil { + t.Fatalf("CFG phase produced MIR: %#v", entry.MIR) } + warnings := 0 for _, item := range diag.Diagnostics() { - if item.Code == test.warning { - return + if item == nil || (item.Code != diagnostics.WarnConstantConditionTrue && item.Code != diagnostics.WarnConstantConditionFalse) { + continue + } + if item.Code != test.warning { + t.Fatalf("wrong constant-condition warning %s:\n%s", item.Code, diag.EmitAllToString()) } + warnings++ + } + if warnings != 1 { + t.Fatalf("got %d warnings %s, want exactly one:\n%s", warnings, test.warning, diag.EmitAllToString()) } - t.Fatalf("missing %s: %s", test.warning, diag.EmitAllToString()) }) } } @@ -1221,8 +1243,7 @@ func TestPipelinePublishesLocalConstantConditions(t *testing.T) { func TestPipelineDefersConstantEvaluationCycleToCFG(t *testing.T) { diag := diagnostics.NewDiagnosticBag() entry := parseModuleSource("entry"+peeper.SourceExt, `fn main() { - const First: bool = Second; - const Second: bool = First; + const First: bool = First; if First { print(1); } }`, diag) entry.Phase = phase.Parsed diff --git a/internal/semantics/typechecker/check_expr.go b/internal/semantics/typechecker/check_expr.go index 5c3cc832..8cb943ca 100644 --- a/internal/semantics/typechecker/check_expr.go +++ b/internal/semantics/typechecker/check_expr.go @@ -408,7 +408,7 @@ func (c *checker) typeBinaryExpr(scope *symbols.Scope, node *ast.BinaryExpr, exp return &typeinfo.InvalidType{} } c.recordImplicitConversion(node.Right, conversion) - if value, ok := c.evaluateConstant(c.ctx, scope, node.Right, right); ok { + if value, ok := c.evaluateConstant(c.ctx, node.Right, right); ok { if count, ok := value.(*constvalue.IntConst); ok && count != nil { _, bits, _ := typeinfo.NumericInfo(left) normalized, normalizedOK := constvalue.NormalizeInteger(count.Int(), @@ -653,7 +653,7 @@ func (c *checker) typeIndexExpr(scope *symbols.Scope, node *ast.IndexExpr) typei return elem } array := typeinfo.Underlying(baseType).(*typeinfo.ArrayType) - value, ok := c.evaluateConstant(c.ctx, scope, node.Index, typeinfo.DefaultIntegerType()) + value, ok := c.evaluateConstant(c.ctx, node.Index, typeinfo.DefaultIntegerType()) if !ok { return elem } diff --git a/internal/semantics/typechecker/check_stmt.go b/internal/semantics/typechecker/check_stmt.go index f3bb0a75..629514ed 100644 --- a/internal/semantics/typechecker/check_stmt.go +++ b/internal/semantics/typechecker/check_stmt.go @@ -572,8 +572,8 @@ func (c *checker) checkForInStmt(scope *symbols.Scope, node *ast.ForStmt, return } } if valid { - startValue, startFound := c.evaluateConstant(c.ctx, scope, rangeExpr.Start, elemType) - endValue, endFound := c.evaluateConstant(c.ctx, scope, rangeExpr.End, elemType) + startValue, startFound := c.evaluateConstant(c.ctx, rangeExpr.Start, elemType) + endValue, endFound := c.evaluateConstant(c.ctx, rangeExpr.End, elemType) start, startIntegral := startValue.(*constvalue.IntConst) end, endIntegral := endValue.(*constvalue.IntConst) if startFound && endFound && startIntegral && endIntegral && start.Int().Cmp(end.Int()) < 0 { diff --git a/internal/semantics/typechecker/constant_eval.go b/internal/semantics/typechecker/constant_eval.go index 2af7b8e9..1b40f8c5 100644 --- a/internal/semantics/typechecker/constant_eval.go +++ b/internal/semantics/typechecker/constant_eval.go @@ -45,26 +45,24 @@ func (e *constantEvaluator) finalizeModuleValues() { return } e.module.SymbolIndex.ClearConstants() - for _, sym := range e.module.ModuleScope.Symbols() { + moduleSymbols := e.module.ModuleScope.Symbols() + // Invalidate every module constant before evaluating any dependency. + for _, sym := range moduleSymbols { if sym != nil && sym.Kind == symbols.SymbolConst { delete(e.cache, sym.ID) } } previous := e.publishModuleValues e.publishModuleValues = true - e.evalModuleConstants() - e.publishModuleValues = previous -} - -func (e *constantEvaluator) evalModuleConstants() { - for _, sym := range e.module.ModuleScope.Symbols() { + for _, sym := range moduleSymbols { if sym != nil && sym.Kind == symbols.SymbolConst { - e.evalConstSymbol(sym, e.module.ModuleScope) + e.evalConstSymbol(sym) } } + e.publishModuleValues = previous } -func (e *constantEvaluator) evalConstSymbol(sym *symbols.Symbol, scope *symbols.Scope) (constvalue.Value, bool) { +func (e *constantEvaluator) evalConstSymbol(sym *symbols.Symbol) (constvalue.Value, bool) { if e == nil || e.module == nil || sym == nil { return nil, false } @@ -94,20 +92,11 @@ func (e *constantEvaluator) evalConstSymbol(sym *symbols.Symbol, scope *symbols. return nil, false } e.inProgress[sym.ID] = struct{}{} - valueScope := scope - if e.module.ModuleScope != nil { - if found, ok := e.module.ModuleScope.LookupLocal(sym.Name); ok && found != nil && found.ID == sym.ID { - valueScope = e.module.ModuleScope - } - } - if valueScope == nil { - valueScope = e.module.ModuleScope - } expected := typeinfo.Type(nil) if sym.Type != nil && !typeinfo.IsInvalidOrUnknown(sym.Type) { expected = sym.Type } - value, ok := e.evalExpr(valueScope, decl.Value, expected) + value, ok := e.evalExpr(decl.Value, expected) delete(e.inProgress, sym.ID) if !ok { return nil, false @@ -123,7 +112,7 @@ func (e *constantEvaluator) evalConstSymbol(sym *symbols.Symbol, scope *symbols. return value, true } -func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expected typeinfo.Type) (constvalue.Value, bool) { +func (e *constantEvaluator) evalExpr(expr ast.Expr, expected typeinfo.Type) (constvalue.Value, bool) { if e.evidence != nil { if construction, ok := e.evidence.VariantConstruction(expr.ID()); ok { if typeinfo.OwnershipCapabilityOf(construction.EnumType).Copy != typeinfo.CopyImplicit { @@ -148,7 +137,7 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect fields := make([]constvalue.Value, len(payload.Fields)) for index, field := range payload.Fields { valueExpr := valuesByName[field.Name] - value, ok := e.evalExpr(scope, valueExpr, field.Type) + value, ok := e.evalExpr(valueExpr, field.Type) if !ok { return nil, false } @@ -156,7 +145,7 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect } return constvalue.NewVariant(descriptor.Identity, typeinfo.TypeText(construction.EnumType), construction.Case, fields) } - value, ok := e.evalExpr(scope, construction.Value, construction.Payload) + value, ok := e.evalExpr(construction.Value, construction.Payload) if !ok { return nil, false } @@ -173,7 +162,7 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect if !found || test.Family != typeinfo.VariantFamilyNamed { return nil, false } - value, ok := e.evalExpr(scope, node.Value, e.evidence.ExprType(node.Value.ID())) + value, ok := e.evalExpr(node.Value, e.evidence.ExprType(node.Value.ID())) variant, isConstant := value.(*constvalue.VariantConst) if !ok || !isConstant || variant == nil { return nil, false @@ -224,15 +213,12 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect case *ast.BoolLit: return constvalue.NewBool(node.Value), true case *ast.Ident: - lookup := scope - if lookup == nil { - lookup = e.module.ModuleScope - } - sym, ok := lookup.Lookup(node.Name) - if !ok || sym == nil || sym.Kind != symbols.SymbolConst { + // Later declarations may shadow the name, but not its resolved binding. + sym := e.module.SymbolIndex.Symbol(node) + if sym == nil || sym.Kind != symbols.SymbolConst { return nil, false } - value, ok := e.evalConstSymbol(sym, lookup) + value, ok := e.evalConstSymbol(sym) if !ok { return nil, false } @@ -251,7 +237,7 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect } // Cast input keeps its own width; destination context must not change // overflow before the explicit finite-width conversion. - value, ok := e.evalExpr(scope, node.Expr, sourceType) + value, ok := e.evalExpr(node.Expr, sourceType) integer, isInteger := value.(*constvalue.IntConst) if !ok || !isInteger || integer == nil { return nil, false @@ -262,14 +248,14 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect } return expectedNumericConstValue(converted, expected) case *ast.UnaryExpr: - value, ok := e.evalExpr(scope, node.Expr, expected) + value, ok := e.evalExpr(node.Expr, expected) if !ok { return nil, false } return constvalue.FoldUnary(node.Op, value) case *ast.BinaryExpr: - left, lok := e.evalExpr(scope, node.Left, expected) - right, rok := e.evalExpr(scope, node.Right, expected) + left, lok := e.evalExpr(node.Left, expected) + right, rok := e.evalExpr(node.Right, expected) if !lok || !rok { return nil, false } @@ -283,8 +269,8 @@ func (e *constantEvaluator) evalExpr(scope *symbols.Scope, expr ast.Expr, expect if commonType == nil { return nil, false } - left, lok = e.evalExpr(scope, node.Left, commonType) - right, rok = e.evalExpr(scope, node.Right, commonType) + left, lok = e.evalExpr(node.Left, commonType) + right, rok = e.evalExpr(node.Right, commonType) if !lok || !rok { return nil, false } diff --git a/internal/semantics/typechecker/constant_eval_test.go b/internal/semantics/typechecker/constant_eval_test.go index 18a07363..5bfa4260 100644 --- a/internal/semantics/typechecker/constant_eval_test.go +++ b/internal/semantics/typechecker/constant_eval_test.go @@ -1,6 +1,7 @@ package typechecker import ( + "fmt" "testing" "compiler/internal/constvalue" @@ -8,6 +9,7 @@ import ( "compiler/internal/frontend/ast" "compiler/internal/frontend/lexer" "compiler/internal/frontend/parser" + "compiler/internal/ir/thir" "compiler/internal/module" "compiler/internal/moduleid" "compiler/internal/project" @@ -224,7 +226,7 @@ func TestFinalizeValuesRecomputesLazyConstantsWithFinalSymbolTypes(t *testing.T) t.Fatal("missing symbol Value") } evaluator := newConstantEvaluator(ctx, module, newEvidence()) - if _, ok := evaluator.evalConstSymbol(sym, module.ModuleScope); !ok { + if _, ok := evaluator.evalConstSymbol(sym); !ok { t.Fatal("failed to lazily evaluate Value") } if _, found := evaluator.cache[sym.ID]; !found { @@ -258,7 +260,7 @@ fn main() { reference := fn.Body.Stmts[1].(*ast.LetDecl).Value.(*ast.Ident) scope := module.SymbolIndex.Scope(fn.Body) evaluator := newConstantEvaluator(ctx, module, newEvidence()) - if _, ok := evaluator.evalExpr(scope, reference, nil); !ok { + if _, ok := evaluator.evalExpr(reference, nil); !ok { t.Fatal("failed to evaluate local constant reference") } localSymbol, found := scope.LookupLocal(local.Name.Name) @@ -280,6 +282,48 @@ fn main() { } } +func TestEvaluateUsesResolvedConstantBindings(t *testing.T) { + for _, shadow := range []string{"const", "let"} { + t.Run(shadow, func(t *testing.T) { + module, diag := checkTypeModule(t, fmt.Sprintf(`const index: i32 = 0; +fn main() { + const saved: i32 = index; + %s index: i32 = 2; + let values = [3]i32{10, 20, 30}; + let selected = values[saved]; + if saved == 0 { print(selected); } +}`, shadow)) + if diag.HasErrors() { + t.Fatalf("unexpected diagnostics:\n%s", diag.EmitAllToString()) + } + fn := module.AST.Stmts[1].(*ast.FnDecl) + index := fn.Body.Stmts[3].(*ast.LetDecl).Value.(*ast.IndexExpr) + checkedIndex := module.THIR.Node(index.ID()).(*thir.Index) + if checkedIndex.Constant == nil || checkedIndex.Constant.Text != "0" { + t.Errorf("constant index = %#v, want resolved outer value 0", checkedIndex.Constant) + } + condition := fn.Body.Stmts[4].(*ast.IfStmt) + checkedCondition := module.THIR.Node(condition.ID()).(*thir.If) + if checkedCondition.ConstantCondition == nil || !*checkedCondition.ConstantCondition { + t.Errorf("constant condition = %v, want true from resolved outer value", checkedCondition.ConstantCondition) + } + }) + } +} + +func TestEvaluateRejectsUnboundConstantReferences(t *testing.T) { + module, diag := constevalModule(t, `const Value: i32 = 7;`) + if diag.HasErrors() { + t.Fatalf("unexpected diagnostics:\n%s", diag.EmitAllToString()) + } + ctx := project.New(".", peeper.SourceExt, diag) + ctx.AddModule(module) + evaluator := newConstantEvaluator(ctx, module, newEvidence()) + if value, ok := evaluator.evalExpr(&ast.Ident{Name: "Value"}, nil); ok { + t.Fatalf("unbound identifier evaluated to %#v through name lookup", value) + } +} + func TestEvaluateReadsForeignPublishedConstantWithoutConsumerCache(t *testing.T) { diag := diagnostics.NewDiagnosticBag() ctx := project.New(".", peeper.SourceExt, diag) @@ -318,7 +362,7 @@ func TestEvaluateReadsForeignPublishedConstantWithoutConsumerCache(t *testing.T) t.Fatal("missing consumer constant") } evaluator := newConstantEvaluator(ctx, consumer, newEvidence()) - if _, ok := evaluator.evalConstSymbol(local, consumer.ModuleScope); !ok { + if _, ok := evaluator.evalConstSymbol(local); !ok { t.Fatal("failed to lazily evaluate imported constant") } cached := evaluator.cache[local.ID] @@ -420,7 +464,7 @@ func TestConstantQueryCacheIsPrivateUntilFinalization(t *testing.T) { } evaluator := newConstantEvaluator(ctx, mod, newEvidence()) - if _, ok := evaluator.evalConstSymbol(sym, mod.ModuleScope); !ok { + if _, ok := evaluator.evalConstSymbol(sym); !ok { t.Fatal("lazy constant evaluation failed") } if got := mod.SymbolIndex.ConstantValue(sym.ID); got != nil { diff --git a/internal/semantics/typechecker/thir_build.go b/internal/semantics/typechecker/thir_build.go index 0e7fc158..a3bb47ab 100644 --- a/internal/semantics/typechecker/thir_build.go +++ b/internal/semantics/typechecker/thir_build.go @@ -18,9 +18,9 @@ func (c *checker) buildTHIR() *thir.Module { if c == nil || c.module == nil || c.module.AST == nil { return nil } - constantCondition := func(expr ast.Expr, scope *symbols.Scope) (*bool, []*diagnostics.Diagnostic) { + constantCondition := func(expr ast.Expr) (*bool, []*diagnostics.Diagnostic) { pending := diagnostics.NewDiagnosticBag() - value, ok := c.evaluateConstant(c.ctx.WithDiagnostics(pending), scope, expr, &typeinfo.BoolType{}) + value, ok := c.evaluateConstant(c.ctx.WithDiagnostics(pending), expr, &typeinfo.BoolType{}) if !ok { return nil, pending.Diagnostics() } @@ -43,8 +43,7 @@ func (c *checker) buildTHIR() *thir.Module { type thirBuilder struct { symbolIndex *symbols.Index typing *evidence - currentScope *symbols.Scope - constantCondition func(ast.Expr, *symbols.Scope) (*bool, []*diagnostics.Diagnostic) + constantCondition func(ast.Expr) (*bool, []*diagnostics.Diagnostic) } func (b *thirBuilder) function(source *ast.FnDecl) *thir.Function { @@ -85,9 +84,6 @@ func (b *thirBuilder) block(source *ast.BlockStmt) *thir.Block { return nil } block := &thir.Block{StmtInfo: stmtInfo(source), Scope: b.scope(source), Stmts: make([]thir.Stmt, 0, len(source.Stmts))} - previousScope := b.currentScope - b.currentScope = block.Scope - defer func() { b.currentScope = previousScope }() for _, statement := range source.Stmts { if lowered := b.statement(statement); lowered != nil { block.Stmts = append(block.Stmts, lowered) @@ -115,7 +111,7 @@ func (b *thirBuilder) statement(statement ast.Stmt) thir.Stmt { case *ast.IfStmt: statement := &thir.If{StmtInfo: stmtInfo(node), Condition: b.expression(node.Cond)} if b.constantCondition != nil { - statement.ConstantCondition, statement.ConditionDiagnostics = b.constantCondition(node.Cond, b.currentScope) + statement.ConstantCondition, statement.ConditionDiagnostics = b.constantCondition(node.Cond) } statement.Then = b.block(node.Then) statement.Else = b.statement(node.Else) diff --git a/internal/semantics/typechecker/typechecker.go b/internal/semantics/typechecker/typechecker.go index 6212dcdc..3f1fef67 100644 --- a/internal/semantics/typechecker/typechecker.go +++ b/internal/semantics/typechecker/typechecker.go @@ -134,7 +134,7 @@ func runCheck(ctx *project.CompilerContext, module *module.Module) (*thir.Module return c.buildTHIR(), evidence } -func (c *checker) evaluateConstant(ctx *project.CompilerContext, scope *symbols.Scope, expr ast.Expr, expected typeinfo.Type) (constvalue.Value, bool) { +func (c *checker) evaluateConstant(ctx *project.CompilerContext, expr ast.Expr, expected typeinfo.Type) (constvalue.Value, bool) { if c == nil || c.module == nil || expr == nil { return nil, false } @@ -142,7 +142,7 @@ func (c *checker) evaluateConstant(ctx *project.CompilerContext, scope *symbols. c.constantEval = newConstantEvaluator(c.ctx, c.module, c.evidence) } evaluator := c.constantEval.withContext(ctx) - return evaluator.evalExpr(scope, expr, expected) + return evaluator.evalExpr(expr, expected) } // CanAdaptFirstCallArgument reports whether argType can occupy a function's diff --git a/x_test/negative_constant_shadow_index/peeper.toml b/x_test/negative_constant_shadow_index/peeper.toml new file mode 100644 index 00000000..185b3a67 --- /dev/null +++ b/x_test/negative_constant_shadow_index/peeper.toml @@ -0,0 +1,7 @@ +name = "negative_constant_shadow_index" +build = "program" + +[test] +mode = "check" +outcome = "failure" +stderr_contains = ["array index out of bounds: index 3 for length 3"] diff --git a/x_test/negative_constant_shadow_index/src/main.peep b/x_test/negative_constant_shadow_index/src/main.peep new file mode 100644 index 00000000..befa7851 --- /dev/null +++ b/x_test/negative_constant_shadow_index/src/main.peep @@ -0,0 +1,9 @@ +fn main() -> i32 { + const index: i32 = 3; + { + const saved: i32 = index; + const index: i32 = 0; + let values = [3]i32{10, 20, 30}; + return values[saved]; + } +} diff --git a/x_test/runtime_constant_shadow/peeper.toml b/x_test/runtime_constant_shadow/peeper.toml new file mode 100644 index 00000000..f1edf985 --- /dev/null +++ b/x_test/runtime_constant_shadow/peeper.toml @@ -0,0 +1,6 @@ +name = "runtime_constant_shadow" +build = "program" + +[test] +mode = "run" +outcome = "success" diff --git a/x_test/runtime_constant_shadow/src/main.peep b/x_test/runtime_constant_shadow/src/main.peep new file mode 100644 index 00000000..ba174fa8 --- /dev/null +++ b/x_test/runtime_constant_shadow/src/main.peep @@ -0,0 +1,24 @@ +fn main() -> i32 { + const index: i32 = 0; + { + const saved: i32 = index; + const index: i32 = 2; + let values = [3]i32{10, 20, 30}; + println(values[saved]); + println(saved); + if values[saved] != 10 || saved != 0 { + return 1; + } + } + { + const saved: i32 = index; + let index: i32 = 2; + let values = [3]i32{10, 20, 30}; + println(values[saved]); + println(saved); + if values[saved] != 10 || saved != 0 { + return 2; + } + } + return 0; +}