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; +}