Эх сурвалжийг харах

Support for name lookup into interfaces. (#3729)

Follow the existing support for classes.

None of this is especially useful until other features land: we don't
yet have any use for defining methods of an interface out of line,
because we don't support `default` or `final` interface methods, and we
don't have impl lookup, so referring to an interface member is also not
especially useful. But this is a nice piece to factor out that's a
prerequisite for effectively testing other interface features.
Richard Smith 2 жил өмнө
parent
commit
90369815ad

+ 33 - 3
toolchain/check/decl_name_stack.cpp

@@ -195,6 +195,21 @@ auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context,
       }
       break;
     }
+    case SemIR::InterfaceDecl::Kind: {
+      const auto& interface_info = context_->interfaces().Get(
+          resolved_inst.As<SemIR::InterfaceDecl>().interface_id);
+      if (interface_info.is_defined()) {
+        name_context.state = NameContext::State::Resolved;
+        name_context.target_scope_id = interface_info.scope_id;
+        if (!is_unqualified) {
+          PushNameQualifierScope(*context_, name_context.resolved_inst_id,
+                                 interface_info.scope_id);
+        }
+      } else {
+        name_context.state = NameContext::State::ResolvedNonScope;
+      }
+      break;
+    }
     case SemIR::Namespace::Kind: {
       auto scope_id = resolved_inst.As<SemIR::Namespace>().name_scope_id;
       name_context.state = NameContext::State::Resolved;
@@ -234,9 +249,8 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
     case NameContext::State::ResolvedNonScope: {
       // Because more qualifiers were found, we diagnose that the earlier
       // qualifier didn't resolve to a scoped entity.
-      if (auto class_decl = context_->insts()
-                                .Get(name_context.resolved_inst_id)
-                                .TryAs<SemIR::ClassDecl>()) {
+      if (auto class_decl = context_->insts().TryGetAs<SemIR::ClassDecl>(
+              name_context.resolved_inst_id)) {
         CARBON_DIAGNOSTIC(QualifiedDeclInIncompleteClassScope, Error,
                           "Cannot declare a member of incomplete class `{0}`.",
                           SemIR::TypeId);
@@ -245,6 +259,22 @@ auto DeclNameStack::TryResolveQualifier(NameContext& name_context,
             context_->classes().Get(class_decl->class_id).self_type_id);
         context_->NoteIncompleteClass(class_decl->class_id, builder);
         builder.Emit();
+      } else if (auto interface_decl =
+                     context_->insts().TryGetAs<SemIR::InterfaceDecl>(
+                         name_context.resolved_inst_id)) {
+        CARBON_DIAGNOSTIC(
+            QualifiedDeclInUndefinedInterfaceScope, Error,
+            "Cannot declare a member of undefined interface `{0}`.",
+            std::string);
+        auto builder = context_->emitter().Build(
+            name_context.parse_node, QualifiedDeclInUndefinedInterfaceScope,
+            context_->sem_ir().StringifyTypeExpr(
+                context_->sem_ir()
+                    .constant_values()
+                    .Get(name_context.resolved_inst_id)
+                    .inst_id()));
+        context_->NoteUndefinedInterface(interface_decl->interface_id, builder);
+        builder.Emit();
       } else {
         CARBON_DIAGNOSTIC(QualifiedNameInNonScope, Error,
                           "Name qualifiers are only allowed for entities that "

+ 17 - 0
toolchain/check/handle_name.cpp

@@ -25,6 +25,8 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
   if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
     return base_as_namespace->name_scope_id;
   }
+  // TODO: Consider refactoring the near-identical class and interface support
+  // below.
   if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
     auto& class_info = context.classes().Get(base_as_class->class_id);
     if (!class_info.is_defined()) {
@@ -39,6 +41,21 @@ static auto GetAsNameScope(Context& context, SemIR::InstId base_id)
     }
     return class_info.scope_id;
   }
+  if (auto base_as_interface = base.TryAs<SemIR::InterfaceType>()) {
+    auto& interface_info =
+        context.interfaces().Get(base_as_interface->interface_id);
+    if (!interface_info.is_defined()) {
+      CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
+                        "Member access into undefined interface `{0}`.",
+                        std::string);
+      auto builder = context.emitter().Build(
+          base_id, QualifiedExprInUndefinedInterfaceScope,
+          context.sem_ir().StringifyTypeExpr(base_id));
+      context.NoteUndefinedInterface(base_as_interface->interface_id, builder);
+      builder.Emit();
+    }
+    return interface_info.scope_id;
+  }
   return std::nullopt;
 }
 

+ 72 - 0
toolchain/check/testdata/interface/fail_add_member_outside_definition.carbon

@@ -0,0 +1,72 @@
+// 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
+//
+// AUTOUPDATE
+
+interface Interface { }
+
+// CHECK:STDERR: fail_add_member_outside_definition.carbon:[[@LINE+3]]:14: ERROR: Out-of-line declaration requires a declaration in scoped entity.
+// CHECK:STDERR: fn Interface.F() {}
+// CHECK:STDERR:              ^
+fn Interface.F() {}
+
+// Nesting interfaces like this is not valid, but make sure we don't crash.
+interface Outer {
+  interface Inner {
+    // CHECK:STDERR: fail_add_member_outside_definition.carbon:[[@LINE+3]]:8: ERROR: Name `Outer` not found.
+    // CHECK:STDERR:     fn Outer.F();
+    // CHECK:STDERR:        ^~~~~
+    fn Outer.F();
+  }
+  // CHECK:STDERR: fail_add_member_outside_definition.carbon:[[@LINE+3]]:12: ERROR: Out-of-line declaration requires a declaration in scoped entity.
+  // CHECK:STDERR:   fn Inner.F();
+  // CHECK:STDERR:            ^
+  fn Inner.F();
+}
+
+// CHECK:STDOUT: --- fail_add_member_outside_definition.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
+// CHECK:STDOUT:   %.2: type = interface_type @Outer [template]
+// CHECK:STDOUT:   %.3: type = interface_type @Inner [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Interface = %Interface.decl, .Outer = %Outer.decl} [template]
+// CHECK:STDOUT:   %Interface.decl = interface_decl @Interface, () [template = constants.%.1]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:   %Outer.decl = interface_decl @Outer, () [template = constants.%.2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = file.%F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Outer {
+// CHECK:STDOUT:   %Inner.decl = interface_decl @Inner, () [template = constants.%.3]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Inner = %Inner.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Inner {
+// CHECK:STDOUT:   %.loc20: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = @Outer.%F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2();
+// CHECK:STDOUT:

+ 81 - 0
toolchain/check/testdata/interface/fail_lookup_undefined.carbon

@@ -0,0 +1,81 @@
+// 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
+//
+// AUTOUPDATE
+
+interface Undefined;
+
+// CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+6]]:4: ERROR: Cannot declare a member of undefined interface `Undefined`.
+// CHECK:STDERR: fn Undefined.F();
+// CHECK:STDERR:    ^~~~~~~~~
+// CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE-5]]:1: Interface was forward declared here.
+// CHECK:STDERR: interface Undefined;
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
+fn Undefined.F();
+
+fn Test() {
+  // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+6]]:3: ERROR: Member access into undefined interface `Undefined`.
+  // CHECK:STDERR:   Undefined.G();
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE-14]]:1: Interface was forward declared here.
+  // CHECK:STDERR: interface Undefined;
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
+  Undefined.G();
+}
+
+interface BeingDefined {
+  // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+9]]:13: ERROR: Member access into undefined interface `BeingDefined`.
+  // CHECK:STDERR:   fn H() -> BeingDefined.T;
+  // CHECK:STDERR:             ^~~~~~~~~~~~
+  // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE-4]]:1: Interface is currently being defined.
+  // CHECK:STDERR: interface BeingDefined {
+  // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+3]]:13: ERROR: Name `T` not found.
+  // CHECK:STDERR:   fn H() -> BeingDefined.T;
+  // CHECK:STDERR:             ^~~~~~~~~~~~~~
+  fn H() -> BeingDefined.T;
+  // CHECK:STDERR: fail_lookup_undefined.carbon:[[@LINE+3]]:6: ERROR: Name `BeingDefined` not found.
+  // CHECK:STDERR:   fn BeingDefined.I();
+  // CHECK:STDERR:      ^~~~~~~~~~~~
+  fn BeingDefined.I();
+}
+
+// CHECK:STDOUT: --- fail_lookup_undefined.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Undefined [template]
+// CHECK:STDOUT:   %.2: type = interface_type @BeingDefined [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Undefined = %Undefined.decl, .Test = %Test, .BeingDefined = %BeingDefined.decl} [template]
+// CHECK:STDOUT:   %Undefined.decl = interface_decl @Undefined, () [template = constants.%.1]
+// CHECK:STDOUT:   %.loc15: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT:   %Test: <function> = fn_decl @Test [template]
+// CHECK:STDOUT:   %BeingDefined.decl = interface_decl @BeingDefined, () [template = constants.%.2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Undefined;
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @BeingDefined {
+// CHECK:STDOUT:   %H: <function> = fn_decl @H [template]
+// CHECK:STDOUT:   %.loc41: <function> = fn_decl @.2 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .H = %H
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Test() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Undefined.ref: type = name_ref Undefined, file.%Undefined.decl [template = constants.%.1]
+// CHECK:STDOUT:   %G.ref: <error> = name_ref G, <error> [template = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H() -> <error>;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.2();
+// CHECK:STDOUT:

+ 43 - 0
toolchain/check/testdata/interface/fail_todo_facet_lookup.carbon

@@ -0,0 +1,43 @@
+// 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
+//
+// AUTOUPDATE
+
+interface Interface { fn F(); }
+
+fn CallStatic(T:! Interface) {
+  // CHECK:STDERR: fail_todo_facet_lookup.carbon:[[@LINE+3]]:3: ERROR: Type `Interface` does not support qualified expressions.
+  // CHECK:STDERR:   T.F();
+  // CHECK:STDERR:   ^~~
+  T.F();
+}
+
+// CHECK:STDOUT: --- fail_todo_facet_lookup.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Interface = %Interface.decl, .CallStatic = %CallStatic} [template]
+// CHECK:STDOUT:   %Interface.decl = interface_decl @Interface, () [template = constants.%.1]
+// CHECK:STDOUT:   %CallStatic: <function> = fn_decl @CallStatic [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallStatic(%T: Interface) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T.ref: Interface = name_ref T, %T [symbolic = %T]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 43 - 0
toolchain/check/testdata/interface/member_lookup.carbon

@@ -0,0 +1,43 @@
+// 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
+//
+// AUTOUPDATE
+
+interface Interface { fn F(); }
+
+fn F() {
+  // TODO: This should not be valid by itself.
+  Interface.F();
+}
+
+// CHECK:STDOUT: --- member_lookup.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.Interface = %Interface.decl, .F = %F} [template]
+// CHECK:STDOUT:   %Interface.decl = interface_decl @Interface, () [template = constants.%.1]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.2 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Interface {
+// CHECK:STDOUT:   %F: <function> = fn_decl @F.1 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .F = %F
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:   %F.ref: <function> = name_ref F, @Interface.%F [template = @Interface.%F]
+// CHECK:STDOUT:   %.loc11: init () = call %F.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -242,9 +242,11 @@ CARBON_DIAGNOSTIC_KIND(ExplicitAsConversionFailure)
 CARBON_DIAGNOSTIC_KIND(TypeExprEvaluationFailure)
 CARBON_DIAGNOSTIC_KIND(QualifiedDeclOutsideScopeEntity)
 CARBON_DIAGNOSTIC_KIND(QualifiedDeclInIncompleteClassScope)
+CARBON_DIAGNOSTIC_KIND(QualifiedDeclInUndefinedInterfaceScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedNameInNonScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedNameNonScopeEntity)
 CARBON_DIAGNOSTIC_KIND(QualifiedExprInIncompleteClassScope)
+CARBON_DIAGNOSTIC_KIND(QualifiedExprInUndefinedInterfaceScope)
 CARBON_DIAGNOSTIC_KIND(QualifiedExprUnsupported)
 CARBON_DIAGNOSTIC_KIND(QualifiedExprNameNotFound)
 CARBON_DIAGNOSTIC_KIND(UseOfNonExprAsValue)