Просмотр исходного кода

Propagate errors in `extend require` up to the containing scope (#6480)

Just as names from an `extend` scope get included in the containing
scope, so do errors. Apply this logic to `extend require impls`,
propagating any errors up.

This is based on #6465.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Dana Jansens 4 месяцев назад
Родитель
Сommit
3c8417947b

+ 8 - 4
toolchain/check/handle_require.cpp

@@ -246,6 +246,7 @@ auto HandleParseNode(Context& context, Parse::RequireDeclId node_id) -> bool {
   auto introducer =
       context.decl_introducer_state_stack().Pop<Lex::TokenKind::Require>();
   LimitModifiersOnDecl(context, introducer, KeywordModifierSet::Extend);
+  bool extend = introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend);
 
   auto scope_inst_id =
       context.node_stack().Pop<Parse::NodeKind::RequireIntroducer>();
@@ -253,6 +254,12 @@ auto HandleParseNode(Context& context, Parse::RequireDeclId node_id) -> bool {
   auto validated = ValidateRequire(context, node_id, self_inst_id,
                                    constraint_inst_id, scope_inst_id);
   if (!validated) {
+    // In an `extend` decl, errors get propagated into the parent scope just as
+    // names do.
+    if (extend) {
+      auto scope_id = context.scope_stack().PeekNameScopeId();
+      context.name_scopes().Get(scope_id).set_has_error();
+    }
     DiscardGenericDecl(context);
     return true;
   }
@@ -260,14 +267,11 @@ auto HandleParseNode(Context& context, Parse::RequireDeclId node_id) -> bool {
   auto [constraint_type_id, identified_facet_type] = *validated;
   if (identified_facet_type->required_interfaces().empty()) {
     // A `require T impls type` adds no actual constraints, so nothing to do.
+    // This is not an error though.
     DiscardGenericDecl(context);
     return true;
   }
 
-  // TODO: When extend is true, any errors should propagate up to the parent
-  // scope.
-  bool extend = introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend);
-
   auto require_impls_decl =
       SemIR::RequireImplsDecl{// To be filled in after.
                               .require_impls_id = SemIR::RequireImplsId::None,

+ 43 - 0
toolchain/check/testdata/facet/require_invalid.carbon

@@ -122,6 +122,35 @@ interface Z {
   // CHECK:STDERR:                  ^~
   // CHECK:STDERR:
   extend require () impls Y;
+
+  fn F() {
+    // The erroneous self-type for an `extend` causes the error to be propagated
+    // into the interface scope, which prevents errors if we fail to find a name
+    // in that scope.
+    Self.A;
+  }
+}
+
+// --- fail_require_with_nonexistent_type.carbon
+library "[[@TEST_NAME]]";
+
+interface Y {}
+
+interface Z {
+  // CHECK:STDERR: fail_require_with_nonexistent_type.carbon:[[@LINE+4]]:11: error: name `nonexistent` not found [NameNotFound]
+  // CHECK:STDERR:   require nonexistent impls Y;
+  // CHECK:STDERR:           ^~~~~~~~~~~
+  // CHECK:STDERR:
+  require nonexistent impls Y;
+
+  fn F() {
+    // The name lookup error still happens, since the `require` is not `extend`.
+    // CHECK:STDERR: fail_require_with_nonexistent_type.carbon:[[@LINE+4]]:5: error: member name `A` not found in `Z` [MemberNameNotFoundInInstScope]
+    // CHECK:STDERR:     Self.A;
+    // CHECK:STDERR:     ^~~~~~
+    // CHECK:STDERR:
+    Self.A;
+  }
 }
 
 // --- fail_extend_require_with_nonexistent_type.carbon
@@ -135,6 +164,13 @@ interface Z {
   // CHECK:STDERR:                  ^~~~~~~~~~~
   // CHECK:STDERR:
   extend require nonexistent impls Y;
+
+  fn F() {
+    // The erroneous self-type for an `extend` causes the error to be propagated
+    // into the interface scope, which prevents errors if we fail to find a name
+    // in that scope.
+    Self.A;
+  }
 }
 
 // --- fail_extend_require_with_nonexistent_type_pointer.carbon
@@ -148,4 +184,11 @@ interface Z {
   // CHECK:STDERR:                  ^~~~~~~~~~~
   // CHECK:STDERR:
   extend require nonexistent* impls Y;
+
+  fn F() {
+    // The erroneous self-type for an `extend` causes the error to be propagated
+    // into the interface scope, which prevents errors if we fail to find a name
+    // in that scope.
+    Self.A;
+  }
 }

+ 1 - 0
toolchain/check/testdata/impl/impl_as_named_constraint.carbon

@@ -198,6 +198,7 @@ impl () as B(1) {}
 // CHECK:STDOUT:     .Self = %Self
 // CHECK:STDOUT:     .A = <poisoned>
 // CHECK:STDOUT:     .X = <poisoned>
+// CHECK:STDOUT:     has_error
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !requires:
 // CHECK:STDOUT:   }

+ 1 - 0
toolchain/check/testdata/interface/incomplete.carbon

@@ -137,6 +137,7 @@ interface A(T:! type) {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = %Self
 // CHECK:STDOUT:   .A = <poisoned>
+// CHECK:STDOUT:   has_error
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT:
 // CHECK:STDOUT: !requires: