From 17e0a1afb91e549fb864387d0bebd9f267320023 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Tue, 30 Nov 2021 13:33:54 -0800 Subject: [PATCH] Implement static name resolution (#958) This doesn't actually use the results of name resolution, but it does verify that they are present. Also ensures that name resolution and type checking are applied to deduced function parameters and the implicit call to `Main()`. Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com> --- executable_semantics/ast/ast.h | 2 + executable_semantics/ast/declaration.h | 11 + executable_semantics/ast/expression.h | 18 ++ executable_semantics/ast/member.h | 5 +- executable_semantics/ast/static_scope.cpp | 33 +++ executable_semantics/ast/static_scope.h | 26 +- .../interpreter/exec_program.cpp | 11 +- .../interpreter/interpreter.cpp | 6 + .../interpreter/resolve_names.cpp | 227 +++++++++++++++++- .../interpreter/type_checker.cpp | 8 +- executable_semantics/interpreter/value.h | 4 +- .../fail_use_before_declare.carbon | 18 ++ 12 files changed, 347 insertions(+), 22 deletions(-) create mode 100644 executable_semantics/testdata/name_lookup/fail_use_before_declare.carbon diff --git a/executable_semantics/ast/ast.h b/executable_semantics/ast/ast.h index 80782cbe139d..5a16cfae9249 100644 --- a/executable_semantics/ast/ast.h +++ b/executable_semantics/ast/ast.h @@ -26,6 +26,8 @@ struct AST { std::vector> declarations; // Names declared at the top level of the file. StaticScope static_scope; + // Synthesized call to `Main`. Injected after parsing. + std::optional> main_call; }; } // namespace Carbon diff --git a/executable_semantics/ast/declaration.h b/executable_semantics/ast/declaration.h index 37a23cc1a2aa..7f19e31ae248 100644 --- a/executable_semantics/ast/declaration.h +++ b/executable_semantics/ast/declaration.h @@ -91,6 +91,7 @@ struct GenericBinding : public virtual AstNode, public NamedEntity { auto name() const -> const std::string& { return name_; } auto type() const -> const Expression& { return *type_; } + auto type() -> Expression& { return *type_; } private: std::string name_; @@ -201,6 +202,9 @@ class FunctionDeclaration : public Declaration { -> llvm::ArrayRef> { return deduced_parameters_; } + auto deduced_parameters() -> llvm::ArrayRef> { + return deduced_parameters_; + } auto param_pattern() const -> const TuplePattern& { return *param_pattern_; } auto param_pattern() -> TuplePattern& { return *param_pattern_; } auto return_term() const -> const ReturnTerm& { return return_term_; } @@ -262,6 +266,7 @@ class AlternativeSignature : public virtual AstNode, public NamedEntity { auto name() const -> const std::string& { return name_; } auto signature() const -> const Expression& { return *signature_; } + auto signature() -> Expression& { return *signature_; } private: std::string name_; @@ -285,6 +290,9 @@ class ChoiceDeclaration : public Declaration { -> llvm::ArrayRef> { return alternatives_; } + auto alternatives() -> llvm::ArrayRef> { + return alternatives_; + } // Contains the alternatives. auto static_scope() const -> const StaticScope& { return static_scope_; } @@ -297,6 +305,9 @@ class ChoiceDeclaration : public Declaration { }; // Global variable definition implements the Declaration concept. +// +// TODO: this should not inherit from NamedEntity, because names should +// always resolve to the underlying binding, not the VariableDeclaration. class VariableDeclaration : public Declaration { public: VariableDeclaration(SourceLocation source_loc, diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index 37c2d15e6436..b4c4bd59eed1 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -21,6 +21,7 @@ namespace Carbon { class Value; +class NamedEntity; class Expression : public virtual AstNode { public: @@ -129,8 +130,25 @@ class IdentifierExpression : public Expression { auto name() const -> const std::string& { return name_; } + // Returns the NamedEntity this identifier refers to. Cannot be called before + // name resolution. + auto named_entity() const -> const NamedEntity& { return **named_entity_; } + + // Sets the value returned by named_entity. Can be called only once, + // during name resolution. + void set_named_entity(Nonnull named_entity) { + CHECK(!named_entity_.has_value()); + named_entity_ = named_entity; + } + + // Returns true if set_named_entity has been called. Should be used only + // for debugging purposes. + // TODO: remove this once we no longer need the CHECKs that use it. + auto has_named_entity() const -> bool { return named_entity_.has_value(); } + private: std::string name_; + std::optional> named_entity_; }; class FieldAccessExpression : public Expression { diff --git a/executable_semantics/ast/member.h b/executable_semantics/ast/member.h index 5ad8ab94bc3f..9ae137b3ce1f 100644 --- a/executable_semantics/ast/member.h +++ b/executable_semantics/ast/member.h @@ -48,7 +48,7 @@ class Member : public virtual AstNode, public NamedEntity { class FieldMember : public Member { public: - FieldMember(SourceLocation source_loc, Nonnull binding) + FieldMember(SourceLocation source_loc, Nonnull binding) : AstNode(AstNodeKind::FieldMember, source_loc), binding_(binding) {} static auto classof(const AstNode* node) -> bool { @@ -56,12 +56,13 @@ class FieldMember : public Member { } auto binding() const -> const BindingPattern& { return *binding_; } + auto binding() -> BindingPattern& { return *binding_; } private: // TODO: split this into a non-optional name and a type, initialized by // a constructor that takes a BindingPattern and handles errors like a // missing name. - Nonnull binding_; + Nonnull binding_; }; } // namespace Carbon diff --git a/executable_semantics/ast/static_scope.cpp b/executable_semantics/ast/static_scope.cpp index bea0949dc075..99406bd8c417 100644 --- a/executable_semantics/ast/static_scope.cpp +++ b/executable_semantics/ast/static_scope.cpp @@ -18,4 +18,37 @@ void StaticScope::Add(std::string name, Nonnull entity) { } } +auto StaticScope::Resolve(const std::string& name, + SourceLocation source_loc) const + -> Nonnull { + std::optional> result = + TryResolve(name, source_loc); + if (!result.has_value()) { + FATAL_COMPILATION_ERROR(source_loc) + << "'" << name << "' is not declared in this scope"; + } + return *result; +} + +auto StaticScope::TryResolve(const std::string& name, + SourceLocation source_loc) const + -> std::optional> { + auto it = declared_names_.find(name); + if (it != declared_names_.end()) { + return it->second; + } + std::optional> result; + for (Nonnull parent : parent_scopes_) { + auto parent_result = parent->TryResolve(name, source_loc); + if (parent_result.has_value() && result.has_value() && + *parent_result != *result) { + FATAL_COMPILATION_ERROR(source_loc) + << "'" << name << "' is ambiguous between " << (*result)->source_loc() + << " and " << (*parent_result)->source_loc(); + } + result = parent_result; + } + return result; +} + } // namespace Carbon diff --git a/executable_semantics/ast/static_scope.h b/executable_semantics/ast/static_scope.h index f68c91a8b7e8..fd76ee4b5706 100644 --- a/executable_semantics/ast/static_scope.h +++ b/executable_semantics/ast/static_scope.h @@ -28,18 +28,38 @@ class NamedEntity : public virtual AstNode { } }; -// The set of declared names in a scope. This is not aware of child scopes, but -// does include directions to parent or related scopes for lookup purposes. +// Maps the names visible in a given scope to the entities they name. +// A scope may have parent scopes, whose names will also be visible in the +// child scope. class StaticScope { public: + // Defines `name` to be `entity` in this scope, or reports a compilation error + // if `name` is already defined in this scope. void Add(std::string name, Nonnull entity); + // Make `parent` a parent of this scope. + // REQUIRES: `parent` is not already a parent of this scope. + void AddParent(Nonnull parent) { + parent_scopes_.push_back(parent); + } + + // Returns the nearest definition of `name` in the ancestor graph of this + // scope, or reports a compilation error at `source_loc` there isn't exactly + // one such definition. + auto Resolve(const std::string& name, SourceLocation source_loc) const + -> Nonnull; + private: + // Equivalent to Resolve, but returns `nullopt` instead of raising an error + // if no definition can be found. Still raises a compilation error if more + // than one definition is found. + auto TryResolve(const std::string& name, SourceLocation source_loc) const + -> std::optional>; + // Maps locally declared names to their entities. std::unordered_map> declared_names_; // A list of scopes used for name lookup within this scope. - // TODO: This is unused, but is intended for name lookup cross-scope. std::vector> parent_scopes_; }; diff --git a/executable_semantics/interpreter/exec_program.cpp b/executable_semantics/interpreter/exec_program.cpp index 88ab6aabf79b..4f65fdf4350f 100644 --- a/executable_semantics/interpreter/exec_program.cpp +++ b/executable_semantics/interpreter/exec_program.cpp @@ -24,6 +24,10 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { } llvm::outs() << "********** type checking **********\n"; } + SourceLocation source_loc("", 0); + ast.main_call = arena->New( + source_loc, arena->New(source_loc, "Main"), + arena->New(source_loc)); // Although name resolution is currently done once, generic programming // (particularly templates) may require more passes. ResolveNames(arena, ast); @@ -37,13 +41,8 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { } llvm::outs() << "********** starting execution **********\n"; } - - SourceLocation source_loc("", 0); - Nonnull call_main = arena->New( - source_loc, arena->New(source_loc, "Main"), - arena->New(source_loc)); int result = - Interpreter(arena, trace).InterpProgram(ast.declarations, call_main); + Interpreter(arena, trace).InterpProgram(ast.declarations, *ast.main_call); llvm::outs() << "result: " << result << "\n"; } diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index 254a6cc95ca0..e301825dbfe9 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -302,6 +302,9 @@ void Interpreter::StepLvalue() { case ExpressionKind::IdentifierExpression: { // { {x :: C, E, F} :: S, H} // -> { {E(x) :: C, E, F} :: S, H} + CHECK(cast(exp).has_named_entity()) + << "Identifier '" << exp << "' at " << exp.source_loc() + << " was not resolved"; Address pointer = GetFromEnv(exp.source_loc(), cast(exp).name()); Nonnull v = arena_->New(pointer); @@ -512,6 +515,9 @@ void Interpreter::StepExp() { case ExpressionKind::IdentifierExpression: { CHECK(act.pos() == 0); const auto& ident = cast(exp); + CHECK(ident.has_named_entity()) + << "Identifier '" << exp << "' at " << exp.source_loc() + << " was not resolved"; // { {x :: C, E, F} :: S, H} -> { {H(E(x)) :: C, E, F} :: S, H} Address pointer = GetFromEnv(exp.source_loc(), ident.name()); return todo_.FinishAction(heap_.Read(pointer, exp.source_loc())); diff --git a/executable_semantics/interpreter/resolve_names.cpp b/executable_semantics/interpreter/resolve_names.cpp index d0636c995983..e7bc9e12131e 100644 --- a/executable_semantics/interpreter/resolve_names.cpp +++ b/executable_semantics/interpreter/resolve_names.cpp @@ -56,6 +56,7 @@ void PopulateNamesInStatement(Arena* arena, case StatementKind::Block: { // Defines a new scope for names. auto& block = cast(statement); + block.static_scope().AddParent(&static_scope); for (const auto& statement : block.statements()) { PopulateNamesInStatement(arena, statement, block.static_scope()); } @@ -91,6 +92,7 @@ void PopulateNamesInStatement(Arena* arena, // Contains blocks. auto& match = cast(statement); for (auto& clause : match.clauses()) { + clause.static_scope().AddParent(&static_scope); PopulateNamesInPattern(clause.pattern(), clause.static_scope()); PopulateNamesInStatement(arena, &clause.statement(), clause.static_scope()); @@ -132,16 +134,18 @@ void PopulateNamesInDeclaration(Arena* arena, Declaration& declaration, switch (declaration.kind()) { case DeclarationKind::FunctionDeclaration: { auto& func = cast(declaration); + func.static_scope().AddParent(&static_scope); static_scope.Add(func.name(), &declaration); for (Nonnull param : func.deduced_parameters()) { func.static_scope().Add(param->name(), param); } PopulateNamesInPattern(func.param_pattern(), func.static_scope()); - PopulateNamesInStatement(arena, func.body(), static_scope); + PopulateNamesInStatement(arena, func.body(), func.static_scope()); break; } case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(declaration); + class_decl.static_scope().AddParent(&static_scope); static_scope.Add(class_decl.name(), &declaration); for (auto* member : class_decl.members()) { PopulateNamesInMember(arena, *member, class_decl.static_scope()); @@ -150,6 +154,7 @@ void PopulateNamesInDeclaration(Arena* arena, Declaration& declaration, } case DeclarationKind::ChoiceDeclaration: { auto& choice = cast(declaration); + choice.static_scope().AddParent(&static_scope); static_scope.Add(choice.name(), &declaration); for (Nonnull alt : choice.alternatives()) { choice.static_scope().Add(alt->name(), alt); @@ -168,19 +173,222 @@ void PopulateNamesInDeclaration(Arena* arena, Declaration& declaration, } } -// TODO: ResolveNames for Expression, Member, Pattern, and Statement will be -// needed for recursion. +// Populates the named_entity member of all IdentifierExpressions in the +// subtree rooted at `expression`, whose nearest enclosing scope is +// `enclosing_scope`. +static void ResolveNamesInExpression(Expression& expression, + const StaticScope& enclosing_scope) { + switch (expression.kind()) { + case ExpressionKind::CallExpression: { + auto& call = cast(expression); + ResolveNamesInExpression(call.function(), enclosing_scope); + ResolveNamesInExpression(call.argument(), enclosing_scope); + break; + } + case ExpressionKind::FunctionTypeLiteral: { + auto& fun_type = cast(expression); + ResolveNamesInExpression(fun_type.parameter(), enclosing_scope); + ResolveNamesInExpression(fun_type.return_type(), enclosing_scope); + break; + } + case ExpressionKind::FieldAccessExpression: + ResolveNamesInExpression( + cast(expression).aggregate(), enclosing_scope); + break; + case ExpressionKind::IndexExpression: { + auto& index = cast(expression); + ResolveNamesInExpression(index.aggregate(), enclosing_scope); + ResolveNamesInExpression(index.offset(), enclosing_scope); + break; + } + case ExpressionKind::PrimitiveOperatorExpression: + for (Nonnull operand : + cast(expression).arguments()) { + ResolveNamesInExpression(*operand, enclosing_scope); + } + break; + case ExpressionKind::TupleLiteral: + for (Nonnull field : + cast(expression).fields()) { + ResolveNamesInExpression(*field, enclosing_scope); + } + break; + case ExpressionKind::StructLiteral: + for (FieldInitializer& init : cast(expression).fields()) { + ResolveNamesInExpression(init.expression(), enclosing_scope); + } + break; + case ExpressionKind::StructTypeLiteral: + for (FieldInitializer& init : + cast(expression).fields()) { + ResolveNamesInExpression(init.expression(), enclosing_scope); + } + break; + case ExpressionKind::IdentifierExpression: { + auto& identifier = cast(expression); + identifier.set_named_entity( + enclosing_scope.Resolve(identifier.name(), identifier.source_loc())); + break; + } + case ExpressionKind::IntrinsicExpression: + // FIXME implement this once #955 is merged + case ExpressionKind::BoolTypeLiteral: + case ExpressionKind::BoolLiteral: + case ExpressionKind::IntTypeLiteral: + case ExpressionKind::ContinuationTypeLiteral: + case ExpressionKind::IntLiteral: + case ExpressionKind::StringLiteral: + case ExpressionKind::StringTypeLiteral: + case ExpressionKind::TypeTypeLiteral: + break; + } +} + +// Equivalent to ResolveNamesInExpression, but operates on patterns. +static void ResolveNamesInPattern(Pattern& pattern, + const StaticScope& enclosing_scope) { + switch (pattern.kind()) { + case PatternKind::BindingPattern: + ResolveNamesInPattern(cast(pattern).type(), + enclosing_scope); + break; + case PatternKind::TuplePattern: + for (Nonnull field : cast(pattern).fields()) { + ResolveNamesInPattern(*field, enclosing_scope); + } + break; + case PatternKind::AlternativePattern: { + auto& alternative = cast(pattern); + ResolveNamesInExpression(alternative.choice_type(), enclosing_scope); + ResolveNamesInPattern(alternative.arguments(), enclosing_scope); + break; + } + case PatternKind::ExpressionPattern: + ResolveNamesInExpression(cast(pattern).expression(), + enclosing_scope); + break; + case PatternKind::AutoPattern: + break; + } +} + +// Equivalent to ResolveNamesInExpression, but operates on statements. +static void ResolveNamesInStatement(Statement& statement, + const StaticScope& enclosing_scope) { + switch (statement.kind()) { + case StatementKind::ExpressionStatement: + ResolveNamesInExpression( + cast(statement).expression(), enclosing_scope); + break; + case StatementKind::Assign: { + auto& assign = cast(statement); + ResolveNamesInExpression(assign.lhs(), enclosing_scope); + ResolveNamesInExpression(assign.rhs(), enclosing_scope); + break; + } + case StatementKind::VariableDefinition: { + auto& def = cast(statement); + ResolveNamesInPattern(def.pattern(), enclosing_scope); + ResolveNamesInExpression(def.init(), enclosing_scope); + break; + } + case StatementKind::If: { + auto& if_stmt = cast(statement); + ResolveNamesInExpression(if_stmt.condition(), enclosing_scope); + ResolveNamesInStatement(if_stmt.then_block(), enclosing_scope); + if (if_stmt.else_block().has_value()) { + ResolveNamesInStatement(**if_stmt.else_block(), enclosing_scope); + } + break; + } + case StatementKind::Return: + ResolveNamesInExpression(cast(statement).expression(), + enclosing_scope); + break; + case StatementKind::Block: { + // TODO: this will resolve usages to declarations that occur later in + // the block. Figure out how to avoid that. + auto& block = cast(statement); + for (Nonnull sub_statement : block.statements()) { + ResolveNamesInStatement(*sub_statement, block.static_scope()); + } + break; + } + case StatementKind::While: { + auto& while_stmt = cast(statement); + ResolveNamesInExpression(while_stmt.condition(), enclosing_scope); + ResolveNamesInStatement(while_stmt.body(), enclosing_scope); + break; + } + case StatementKind::Match: { + auto& match = cast(statement); + ResolveNamesInExpression(match.expression(), enclosing_scope); + for (Match::Clause& clause : match.clauses()) { + ResolveNamesInPattern(clause.pattern(), clause.static_scope()); + ResolveNamesInStatement(clause.statement(), clause.static_scope()); + } + break; + } + case StatementKind::Continuation: + ResolveNamesInStatement(cast(statement).body(), + enclosing_scope); + break; + case StatementKind::Run: + ResolveNamesInExpression(cast(statement).argument(), + enclosing_scope); + break; + case StatementKind::Await: + case StatementKind::Break: + case StatementKind::Continue: + break; + } +} // Recurses through a declaration to find and resolve IdentifierExpressions // using declared_names. void ResolveNamesInDeclaration(Declaration& declaration, - const StaticScope& static_scope) { + const StaticScope& enclosing_scope) { switch (declaration.kind()) { - case DeclarationKind::FunctionDeclaration: - case DeclarationKind::ClassDeclaration: - case DeclarationKind::ChoiceDeclaration: - case DeclarationKind::VariableDeclaration: + case DeclarationKind::FunctionDeclaration: { + auto& function = cast(declaration); + for (Nonnull binding : function.deduced_parameters()) { + ResolveNamesInExpression(binding->type(), function.static_scope()); + } + ResolveNamesInPattern(function.param_pattern(), function.static_scope()); + if (function.return_term().type_expression().has_value()) { + ResolveNamesInExpression(**function.return_term().type_expression(), + function.static_scope()); + } + if (function.body().has_value()) { + ResolveNamesInStatement(**function.body(), function.static_scope()); + } break; + } + case DeclarationKind::ClassDeclaration: { + auto& class_decl = cast(declaration); + for (Nonnull member : class_decl.members()) { + switch (member->kind()) { + case MemberKind::FieldMember: + ResolveNamesInPattern(cast(member)->binding(), + class_decl.static_scope()); + } + } + break; + } + case DeclarationKind::ChoiceDeclaration: { + auto& choice = cast(declaration); + for (Nonnull alternative : choice.alternatives()) { + ResolveNamesInExpression(alternative->signature(), + choice.static_scope()); + } + break; + } + case DeclarationKind::VariableDeclaration: { + auto& var = cast(declaration); + ResolveNamesInPattern(var.binding(), enclosing_scope); + ResolveNamesInExpression(var.initializer(), enclosing_scope); + break; + } } } @@ -193,6 +401,9 @@ void ResolveNames(Arena* arena, AST& ast) { for (auto declaration : ast.declarations) { ResolveNamesInDeclaration(*declaration, ast.static_scope); } + if (ast.main_call.has_value()) { + ResolveNamesInExpression(**ast.main_call, ast.static_scope); + } } } // namespace Carbon diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index ce2fea5b9e1f..ef490e2d0922 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -569,6 +569,8 @@ auto TypeChecker::TypeCheckExp(Nonnull e, TypeEnv types, } case ExpressionKind::IdentifierExpression: { auto& ident = cast(*e); + CHECK(ident.has_named_entity()) << "Identifier '" << *e << "' at " + << e->source_loc() << " was not resolved"; std::optional> type = types.Get(ident.name()); if (type) { SetStaticType(&ident, *type); @@ -1053,7 +1055,8 @@ auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, TypeEnv types, Env values, bool check_body) -> TCResult { // Bring the deduced parameters into scope - for (Nonnull deduced : f->deduced_parameters()) { + for (Nonnull deduced : f->deduced_parameters()) { + TypeCheckExp(&deduced->type(), types, values); // auto t = interpreter_.InterpExp(values, deduced.type); types.Set(deduced->name(), arena_->New(deduced->name())); AllocationId a = interpreter_.AllocateValue(*types.Get(deduced->name())); @@ -1160,6 +1163,9 @@ void TypeChecker::TypeCheck(AST& ast) { for (const auto decl : ast.declarations) { TypeCheckDeclaration(decl, top, ct_top); } + if (ast.main_call.has_value()) { + TypeCheckExp(*ast.main_call, p.types, p.values); + } } void TypeChecker::TypeCheckDeclaration(Nonnull d, diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index a16d69c4a1da..53bdaacbfeb9 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -335,11 +335,11 @@ class TypeType : public Value { // A function type. class FunctionType : public Value { public: - FunctionType(std::vector> deduced, + FunctionType(llvm::ArrayRef> deduced, Nonnull parameters, Nonnull return_type) : Value(Kind::FunctionType), - deduced_(std::move(deduced)), + deduced_(deduced), parameters_(parameters), return_type_(return_type) {} diff --git a/executable_semantics/testdata/name_lookup/fail_use_before_declare.carbon b/executable_semantics/testdata/name_lookup/fail_use_before_declare.carbon new file mode 100644 index 000000000000..361858b83ba4 --- /dev/null +++ b/executable_semantics/testdata/name_lookup/fail_use_before_declare.carbon @@ -0,0 +1,18 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: not executable_semantics %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes=false %s +// RUN: not executable_semantics --trace %s 2>&1 | \ +// RUN: FileCheck --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: executable_semantics %s +// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/name_lookup/fail_use_before_declare.carbon:15: could not find `x` + +package ExecutableSemanticsTest api; + +fn Main() -> i32 { + x = 0; + var x: i32 = 0; + return 0; +}