Skip to content

Commit

Permalink
Parse all kinds of declarations at function scope. (carbon-language#4779
Browse files Browse the repository at this point in the history
)

These don't fully work in check and beyond yet, because they're not
added into lexical lookup, but already mostly do the right thing.

Per carbon-language#3407, disallow namespace declarations anywhere other than at file
scope for now.

We don't treat statements starting with a packaging introducer keyword
(`package`, `library`, `import`) as declarations because they're
sufficiently unlikely to occur that the error recovery doesn't seem
important, and this avoids needing to disambiguate `package.` at the
start of an expression.
  • Loading branch information
zygoloid authored Jan 10, 2025
1 parent 1e5e2bc commit d42128e
Show file tree
Hide file tree
Showing 25 changed files with 1,223 additions and 116 deletions.
12 changes: 12 additions & 0 deletions toolchain/check/handle_namespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/name_scope.h"
#include "toolchain/sem_ir/typed_insts.h"

namespace Carbon::Check {

Expand All @@ -26,6 +27,12 @@ auto HandleParseNode(Context& context, Parse::NamespaceStartId /*node_id*/)
return true;
}

static auto IsNamespaceScope(Context& context, SemIR::NameScopeId name_scope_id)
-> bool {
auto [_, inst] = context.name_scopes().GetInstIfValid(name_scope_id);
return inst && inst->Is<SemIR::Namespace>();
}

auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
auto name_context = context.decl_name_stack().FinishName(
PopNameComponentWithoutParams(context, Lex::TokenKind::Namespace));
Expand Down Expand Up @@ -83,6 +90,11 @@ auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
namespace_inst.name_scope_id = context.name_scopes().Add(
namespace_id, name_context.name_id_for_new_inst(),
name_context.parent_scope_id);
if (!IsNamespaceScope(context, name_context.parent_scope_id)) {
CARBON_DIAGNOSTIC(NamespaceDeclNotAtTopLevel, Error,
"`namespace` declaration not at top level");
context.emitter().Emit(node_id, NamespaceDeclNotAtTopLevel);
}
}

context.ReplaceInstBeforeConstantUse(namespace_id, namespace_inst);
Expand Down
67 changes: 66 additions & 1 deletion toolchain/check/testdata/class/adapter/adapt.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,30 @@ class AdaptNotExtend {

fn F(a: AdaptNotExtend) {
// `Adapted` is not extended, so lookup for `F` finds nothing.
// CHECK:STDERR: fail_not_extend.carbon:[[@LINE+3]]:3: error: member name `F` not found in `AdaptNotExtend` [MemberNameNotFoundInScope]
// CHECK:STDERR: fail_not_extend.carbon:[[@LINE+4]]:3: error: member name `F` not found in `AdaptNotExtend` [MemberNameNotFoundInScope]
// CHECK:STDERR: a.F();
// CHECK:STDERR: ^~~
// CHECK:STDERR:
a.F();
}

// --- fail_misplaced.carbon

fn F() {
// CHECK:STDERR: fail_misplaced.carbon:[[@LINE+4]]:3: error: `adapt` declaration outside class [ClassSpecificDeclOutsideClass]
// CHECK:STDERR: adapt i32;
// CHECK:STDERR: ^~~~~~~~~~
// CHECK:STDERR:
adapt i32;
}

interface I {
// CHECK:STDERR: fail_misplaced.carbon:[[@LINE+3]]:3: error: `adapt` declaration outside class [ClassSpecificDeclOutsideClass]
// CHECK:STDERR: adapt i32;
// CHECK:STDERR: ^~~~~~~~~~
adapt i32;
}

// CHECK:STDOUT: --- basic.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
Expand Down Expand Up @@ -184,3 +202,50 @@ fn F(a: AdaptNotExtend) {
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_misplaced.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [template]
// CHECK:STDOUT: %I.type: type = facet_type <@I> [template]
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int = %import_ref.187
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: .I = %I.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @I {
// CHECK:STDOUT: %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = %Self
// CHECK:STDOUT: witness = ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
64 changes: 57 additions & 7 deletions toolchain/check/testdata/class/fail_base_misplaced.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,55 @@ base class B {}
extend base: B;

fn F() {
// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+7]]:3: error: expected expression [ExpectedExpr]
// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+4]]:3: error: `base` declaration outside class [ClassSpecificDeclOutsideClass]
// CHECK:STDERR: extend base: B;
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR: ^~~~~~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:3: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
// CHECK:STDERR: extend base: B;
// CHECK:STDERR: ^~~~~~
extend base: B;
}

class C {
fn F() {
// CHECK:STDERR: fail_base_misplaced.carbon:[[@LINE+3]]:5: error: `base` declaration outside class [ClassSpecificDeclOutsideClass]
// CHECK:STDERR: extend base: B;
// CHECK:STDERR: ^~~~~~~~~~~~~~~
extend base: B;
}
}

// CHECK:STDOUT: --- fail_base_misplaced.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %B: type = class_type @B [template]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
// CHECK:STDOUT: %F.type.b25: type = fn_type @F.1 [template]
// CHECK:STDOUT: %F.c41: %F.type.b25 = struct_value () [template]
// CHECK:STDOUT: %C: type = class_type @C [template]
// CHECK:STDOUT: %F.type.675: type = fn_type @F.2 [template]
// CHECK:STDOUT: %F.2c9: %F.type.675 = struct_value () [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {}
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .B = %B.decl
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: .C = %C.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {} {}
// CHECK:STDOUT: %B.ref: type = name_ref B, %B.decl [template = constants.%B]
// CHECK:STDOUT: %F.decl: %F.type.b25 = fn_decl @F.1 [template = constants.%F.c41] {} {}
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @B {
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
Expand All @@ -45,5 +75,25 @@ fn F() {
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F();
// CHECK:STDOUT: class @C {
// CHECK:STDOUT: %F.decl: %F.type.675 = fn_decl @F.2 [template = constants.%F.2c9] {} {}
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%C
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F.1() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F.2() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %B.ref: type = name_ref B, file.%B.decl [template = constants.%B]
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
119 changes: 103 additions & 16 deletions toolchain/check/testdata/class/fail_todo_local_class.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,132 @@
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/fail_todo_local_class.carbon

// TODO: Support parsing classes at function scope.
class A {
fn F() {
// CHECK:STDERR: fail_todo_local_class.carbon:[[@LINE+7]]:5: error: expected expression [ExpectedExpr]
// CHECK:STDERR: class B {
// CHECK:STDERR: ^~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_local_class.carbon:[[@LINE+3]]:5: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
// CHECK:STDERR: class B {
// CHECK:STDERR: ^~~~~
fn F() -> i32 {
class B {
fn G() {
var b: B = {};
fn Make() -> Self {
returned var b: Self = {.n = 1};
return var;
}

var n: i32;
}

// TODO: Add the name `B` to the lexical scope.
// CHECK:STDERR: fail_todo_local_class.carbon:[[@LINE+3]]:12: error: name `B` not found [NameNotFound]
// CHECK:STDERR: return B.Make().n;
// CHECK:STDERR: ^
return B.Make().n;
}
}

// CHECK:STDOUT: --- fail_todo_local_class.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %A: type = class_type @A [template]
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
// CHECK:STDOUT: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [template]
// CHECK:STDOUT: %B: type = class_type @B [template]
// CHECK:STDOUT: %Make.type: type = fn_type @Make [template]
// CHECK:STDOUT: %Make: %Make.type = struct_value () [template]
// CHECK:STDOUT: %B.elem: type = unbound_element_type %B, %i32 [template]
// CHECK:STDOUT: %struct_type.n.5cd: type = struct_type {.n: %i32} [template]
// CHECK:STDOUT: %complete_type.9b7: <witness> = complete_type_witness %struct_type.n.5cd [template]
// CHECK:STDOUT: %int_1.5b8: Core.IntLiteral = int_value 1 [template]
// CHECK:STDOUT: %struct_type.n.44a: type = struct_type {.n: Core.IntLiteral} [template]
// CHECK:STDOUT: %Convert.type.cd1: type = fn_type @Convert.1, @ImplicitAs(%i32) [template]
// CHECK:STDOUT: %impl_witness.5b0: <witness> = impl_witness (imports.%import_ref.723), @impl.1(%int_32) [template]
// CHECK:STDOUT: %Convert.type.466: type = fn_type @Convert.2, @impl.1(%int_32) [template]
// CHECK:STDOUT: %Convert.925: %Convert.type.466 = struct_value () [template]
// CHECK:STDOUT: %Convert.bound: <bound method> = bound_method %int_1.5b8, %Convert.925 [template]
// CHECK:STDOUT: %Convert.specific_fn: <specific function> = specific_function %Convert.bound, @Convert.2(%int_32) [template]
// CHECK:STDOUT: %int_1.c60: %i32 = int_value 1 [template]
// CHECK:STDOUT: %B.val: %B = struct_value (%int_1.c60) [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {}
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int = %import_ref.187
// CHECK:STDOUT: .ImplicitAs = %import_ref.a69
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/...
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .A = %A.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %A.decl: type = class_decl @A [template = constants.%A] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @A {
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {
// CHECK:STDOUT: %return.patt: %i32 = return_slot_pattern
// CHECK:STDOUT: %return.param_patt: %i32 = out_param_pattern %return.patt, runtime_param0
// CHECK:STDOUT: } {
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(constants.%int_32) [template = constants.%i32]
// CHECK:STDOUT: %return.param: ref %i32 = out_param runtime_param0
// CHECK:STDOUT: %return: ref %i32 = return_slot %return.param
// CHECK:STDOUT: }
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type.357]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%A
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F();
// CHECK:STDOUT: class @B {
// CHECK:STDOUT: %Make.decl: %Make.type = fn_decl @Make [template = constants.%Make] {
// CHECK:STDOUT: %return.patt: %B = return_slot_pattern
// CHECK:STDOUT: %return.param_patt: %B = out_param_pattern %return.patt, runtime_param0
// CHECK:STDOUT: } {
// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%B [template = constants.%B]
// CHECK:STDOUT: %return.param: ref %B = out_param runtime_param0
// CHECK:STDOUT: %return: ref %B = return_slot %return.param
// CHECK:STDOUT: }
// CHECK:STDOUT: %.loc19: %B.elem = field_decl n, element0 [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %struct_type.n.5cd [template = constants.%complete_type.9b7]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%B
// CHECK:STDOUT: .Make = %Make.decl
// CHECK:STDOUT: .n = %.loc19
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() -> %i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %B.decl: type = class_decl @B [template = constants.%B] {} {}
// CHECK:STDOUT: %B.ref: <error> = name_ref B, <error> [template = <error>]
// CHECK:STDOUT: %Make.ref: <error> = name_ref Make, <error> [template = <error>]
// CHECK:STDOUT: %n.ref: <error> = name_ref n, <error> [template = <error>]
// CHECK:STDOUT: return <error>
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Make() -> %return.param_patt: %B {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %b: ref %B = bind_name b, %return
// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1.5b8]
// CHECK:STDOUT: %.loc15_39.1: %struct_type.n.44a = struct_literal (%int_1)
// CHECK:STDOUT: %impl.elem0: %Convert.type.cd1 = impl_witness_access constants.%impl_witness.5b0, element0 [template = constants.%Convert.925]
// CHECK:STDOUT: %Convert.bound: <bound method> = bound_method %int_1, %impl.elem0 [template = constants.%Convert.bound]
// CHECK:STDOUT: %Convert.specific_fn: <specific function> = specific_function %Convert.bound, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn]
// CHECK:STDOUT: %int.convert_checked: init %i32 = call %Convert.specific_fn(%int_1) [template = constants.%int_1.c60]
// CHECK:STDOUT: %.loc15_39.2: init %i32 = converted %int_1, %int.convert_checked [template = constants.%int_1.c60]
// CHECK:STDOUT: %.loc15_39.3: ref %i32 = class_element_access %return, element0
// CHECK:STDOUT: %.loc15_39.4: init %i32 = initialize_from %.loc15_39.2 to %.loc15_39.3 [template = constants.%int_1.c60]
// CHECK:STDOUT: %.loc15_39.5: init %B = class_init (%.loc15_39.4), %return [template = constants.%B.val]
// CHECK:STDOUT: %.loc15_40: init %B = converted %.loc15_39.1, %.loc15_39.5 [template = constants.%B.val]
// CHECK:STDOUT: assign %return, %.loc15_40
// CHECK:STDOUT: return %b to %return
// CHECK:STDOUT: }
// CHECK:STDOUT:
Loading

0 comments on commit d42128e

Please sign in to comment.