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

Fix parse support for 'fn F[];' (#5135)

According to approved syntax at
https://github.com/carbon-language/carbon-lang/blob/trunk/proposals/p3848.md#syntax-defined,
`fn F[]` without explicit parameters should be valid. This makes it
work, then adds some validation to prevent `class C[]` in check.

Note that for `fn`, positional parameters are a TODO -- but this allows
me to test validation in `fn destroy[]` which is rejected, not just a
TODO.
Jon Ross-Perkins 1 жил өмнө
parent
commit
8738497301

+ 13 - 0
toolchain/check/generic.cpp

@@ -532,4 +532,17 @@ auto GetInstForSpecific(Context& context, SemIR::SpecificId specific_id)
   }
 }
 
+auto DiagnoseIfGenericMissingExplicitParameters(
+    Context& context, SemIR::EntityWithParamsBase& entity_base) -> void {
+  if (!entity_base.implicit_param_patterns_id.has_value() ||
+      entity_base.param_patterns_id.has_value()) {
+    return;
+  }
+
+  CARBON_DIAGNOSTIC(GenericMissingExplicitParameters, Error,
+                    "expected explicit parameters after implicit parameters");
+  context.emitter().Emit(entity_base.last_param_node_id,
+                         GenericMissingExplicitParameters);
+}
+
 }  // namespace Carbon::Check

+ 6 - 0
toolchain/check/generic.h

@@ -6,6 +6,7 @@
 #define CARBON_TOOLCHAIN_CHECK_GENERIC_H_
 
 #include "toolchain/check/context.h"
+#include "toolchain/sem_ir/entity_with_params_base.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
@@ -87,6 +88,11 @@ auto ResolveSpecificDefinition(Context& context, SemIRLoc loc,
 auto GetInstForSpecific(Context& context, SemIR::SpecificId specific_id)
     -> SemIR::InstId;
 
+// Diagnoses if an entity has implicit parameters, indicating it's generic, but
+// is missing explicit parameters.
+auto DiagnoseIfGenericMissingExplicitParameters(
+    Context& context, SemIR::EntityWithParamsBase& entity_base) -> void;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_GENERIC_H_

+ 2 - 0
toolchain/check/handle_class.cpp

@@ -233,6 +233,8 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
        .self_type_id = SemIR::TypeId::None,
        .inheritance_kind = inheritance_kind}};
 
+  DiagnoseIfGenericMissingExplicitParameters(context, class_info);
+
   MergeOrAddName(context, node_id, name_context, class_decl_id, class_decl,
                  class_info, is_definition,
                  introducer.modifier_set.GetAccessKind());

+ 7 - 5
toolchain/check/handle_function.cpp

@@ -385,11 +385,13 @@ static auto ValidateIfDestroy(Context& context, bool is_redecl,
     return;
   }
 
-  CARBON_CHECK(
-      orig_param_patterns_id.has_value(),
-      "TODO: Positional parameters are currently rejected as part of requiring "
-      "implicit parameters, because it's an invalid parse tree; add a "
-      "diagnostic once this is testable");
+  if (!orig_param_patterns_id.has_value()) {
+    CARBON_DIAGNOSTIC(DestroyFunctionPositionalParams, Error,
+                      "missing empty explicit parameter list");
+    context.emitter().Emit(function_info.latest_decl_id(),
+                           DestroyFunctionPositionalParams);
+    return;
+  }
 
   if (orig_param_patterns_id != SemIR::InstBlockId::Empty) {
     CARBON_DIAGNOSTIC(DestroyFunctionNonEmptyExplicitParams, Error,

+ 2 - 0
toolchain/check/handle_interface.cpp

@@ -62,6 +62,8 @@ static auto BuildInterfaceDecl(Context& context,
       name, interface_decl_id, /*is_extern=*/false,
       SemIR::LibraryNameId::None)};
 
+  DiagnoseIfGenericMissingExplicitParameters(context, interface_info);
+
   // Check whether this is a redeclaration.
   SemIR::ScopeLookupResult lookup_result =
       context.decl_name_stack().LookupOrAddName(

+ 8 - 8
toolchain/check/name_component.cpp

@@ -15,20 +15,20 @@ auto PopNameComponent(Context& context, SemIR::InstId return_slot_pattern_id)
   Parse::NodeId last_param_node_id = Parse::NoneNodeId();
 
   // Explicit params.
-  auto [params_loc_id, param_patterns_id] =
+  auto [params_node_id, param_patterns_id] =
       context.node_stack()
           .PopWithNodeIdIf<Parse::NodeKind::ExplicitParamList>();
   if (param_patterns_id) {
     first_param_node_id =
         context.node_stack()
             .PopForSoloNodeId<Parse::NodeKind::ExplicitParamListStart>();
-    last_param_node_id = params_loc_id;
+    last_param_node_id = params_node_id;
   } else {
     param_patterns_id = SemIR::InstBlockId::None;
   }
 
   // Implicit params.
-  auto [implicit_params_loc_id, implicit_param_patterns_id] =
+  auto [implicit_params_node_id, implicit_param_patterns_id] =
       context.node_stack()
           .PopWithNodeIdIf<Parse::NodeKind::ImplicitParamList>();
   if (implicit_param_patterns_id) {
@@ -37,9 +37,9 @@ auto PopNameComponent(Context& context, SemIR::InstId return_slot_pattern_id)
         context.node_stack()
             .PopForSoloNodeId<Parse::NodeKind::ImplicitParamListStart>();
     // Only use the end of implicit params if there weren't explicit params.
-    CARBON_CHECK(last_param_node_id.has_value(),
-                 "Implicit parameters currently only occur before explicit "
-                 "parameters, update and test when this changes");
+    if (!last_param_node_id.has_value()) {
+      last_param_node_id = implicit_params_node_id;
+    }
   } else {
     implicit_param_patterns_id = SemIR::InstBlockId::None;
   }
@@ -64,9 +64,9 @@ auto PopNameComponent(Context& context, SemIR::InstId return_slot_pattern_id)
       .name_id = name_id,
       .first_param_node_id = first_param_node_id,
       .last_param_node_id = last_param_node_id,
-      .implicit_params_loc_id = implicit_params_loc_id,
+      .implicit_params_loc_id = implicit_params_node_id,
       .implicit_param_patterns_id = *implicit_param_patterns_id,
-      .params_loc_id = params_loc_id,
+      .params_loc_id = params_node_id,
       .param_patterns_id = *param_patterns_id,
       .call_params_id = call_params_id,
       .return_slot_pattern_id = return_slot_pattern_id,

+ 24 - 7
toolchain/check/testdata/class/no_prelude/destroy.carbon

@@ -73,11 +73,7 @@ class C {
 library "[[@TEST_NAME]]";
 
 class C {
-  // CHECK:STDERR: fail_positional_params.carbon:[[@LINE+8]]:25: error: a `(` for parameters is required after implicit parameters [ParamsRequiredAfterImplicit]
-  // CHECK:STDERR:   fn destroy[self: Self];
-  // CHECK:STDERR:                         ^
-  // CHECK:STDERR:
-  // CHECK:STDERR: fail_positional_params.carbon:[[@LINE+4]]:3: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR: fail_positional_params.carbon:[[@LINE+4]]:3: error: missing empty explicit parameter list [DestroyFunctionPositionalParams]
   // CHECK:STDERR:   fn destroy[self: Self];
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
@@ -435,17 +431,38 @@ fn C.destroy.Foo() {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
+// CHECK:STDOUT:   %destroy: %destroy.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: file {}
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   complete_type_witness = invalid
+// CHECK:STDOUT:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
+// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %C = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
+// CHECK:STDOUT:     %self: %C = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .destroy = %destroy.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]();
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_explicit_params.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 68 - 0
toolchain/check/testdata/class/no_prelude/generic_vs_params.carbon

@@ -54,6 +54,26 @@ fn F(T:! type) {
   A(T);
 }
 
+// --- fail_implicit_params_only_empty.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_implicit_params_only_empty.carbon:[[@LINE+4]]:10: error: expected explicit parameters after implicit parameters [GenericMissingExplicitParameters]
+// CHECK:STDERR: class Foo[];
+// CHECK:STDERR:          ^~
+// CHECK:STDERR:
+class Foo[];
+
+// --- fail_implicit_params_only.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_implicit_params_only.carbon:[[@LINE+4]]:10: error: expected explicit parameters after implicit parameters [GenericMissingExplicitParameters]
+// CHECK:STDERR: class Foo[T:! type];
+// CHECK:STDERR:          ^~~~~~~~~~
+// CHECK:STDERR:
+class Foo[T:! type];
+
 // CHECK:STDOUT: --- params.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -412,3 +432,51 @@ fn F(T:! type) {
 // CHECK:STDOUT:   %T.patt.loc11_6.2 => constants.%T
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_implicit_params_only_empty.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Foo.type: type = generic_class_type @Foo [concrete]
+// CHECK:STDOUT:   %Foo.generic: %Foo.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Foo = %Foo.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Foo.decl: %Foo.type = class_decl @Foo [concrete = constants.%Foo.generic] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Foo;
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_implicit_params_only.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %Foo.type: type = generic_class_type @Foo [concrete]
+// CHECK:STDOUT:   %Foo.generic: %Foo.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Foo = %Foo.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Foo.decl: %Foo.type = class_decl @Foo [concrete = constants.%Foo.generic] {
+// CHECK:STDOUT:     %T.patt.loc8_11.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc8_11.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc8_11.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc8_11.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @Foo(%T.loc8_11.1: type) {
+// CHECK:STDOUT:   %T.loc8_11.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc8_11.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc8_11.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc8_11.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Foo(constants.%T) {
+// CHECK:STDOUT:   %T.loc8_11.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc8_11.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 34 - 0
toolchain/check/testdata/function/declaration/no_prelude/fail_todo_no_params.carbon

@@ -36,6 +36,15 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR:
 fn A -> ();
 
+// --- fail_todo_implicit_only.carbon
+
+library "[[@TEST_NAME]]";
+// CHECK:STDERR: fail_todo_implicit_only.carbon:[[@LINE+4]]:1: error: semantics TODO: `function with positional parameters` [SemanticsTodo]
+// CHECK:STDERR: fn A[] -> ();
+// CHECK:STDERR: ^~~~~~~~~~~~~
+// CHECK:STDERR:
+fn A[] -> ();
+
 // --- fail_todo_arrow_body.carbon
 
 // TODO: We don't have parsing support for this yet.
@@ -128,6 +137,31 @@ fn A {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @A() -> %empty_tuple.type;
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_implicit_only.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %A.type: type = fn_type @A [concrete]
+// CHECK:STDOUT:   %A: %A.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: %A.type = fn_decl @A [concrete = constants.%A] {
+// CHECK:STDOUT:     %return.patt: %empty_tuple.type = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %empty_tuple.type = out_param_pattern %return.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc7_12.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc7_12.2: type = converted %.loc7_12.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:     %return.param: ref %empty_tuple.type = out_param call_param0
+// CHECK:STDOUT:     %return: ref %empty_tuple.type = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A[]() -> %empty_tuple.type;
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_arrow_body.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {}

+ 62 - 0
toolchain/check/testdata/function/definition/no_prelude/syntactic_merge.carbon

@@ -46,6 +46,31 @@ fn Foo(a: C);
 // CHECK:STDERR:
 fn Foo(a: (C)) {}
 
+// --- fail_only_implicit_params.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {}
+
+// CHECK:STDERR: fail_only_implicit_params.carbon:[[@LINE+8]]:8: error: implicit parameters of functions must be constant or `self` [ImplictParamMustBeConstant]
+// CHECK:STDERR: fn Foo[a: C];
+// CHECK:STDERR:        ^~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_only_implicit_params.carbon:[[@LINE+4]]:1: error: semantics TODO: `function with positional parameters` [SemanticsTodo]
+// CHECK:STDERR: fn Foo[a: C];
+// CHECK:STDERR: ^~~~~~~~~~~~~
+// CHECK:STDERR:
+fn Foo[a: C];
+// CHECK:STDERR: fail_only_implicit_params.carbon:[[@LINE+8]]:8: error: implicit parameters of functions must be constant or `self` [ImplictParamMustBeConstant]
+// CHECK:STDERR: fn Foo[a: (C)] {}
+// CHECK:STDERR:        ^~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_only_implicit_params.carbon:[[@LINE+4]]:1: error: semantics TODO: `function with positional parameters` [SemanticsTodo]
+// CHECK:STDERR: fn Foo[a: (C)] {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn Foo[a: (C)] {}
+
 // --- todo_fail_raw_identifier.carbon
 
 library "[[@TEST_NAME]]";
@@ -341,6 +366,43 @@ fn Foo(a: const (const C)) {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_only_implicit_params.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %Foo.type.a02530.1: type = fn_type @Foo.1 [concrete]
+// CHECK:STDOUT:   %Foo.ddeb7d.1: %Foo.type.a02530.1 = struct_value () [concrete]
+// CHECK:STDOUT:   %Foo.type.a02530.2: type = fn_type @Foo.2 [concrete]
+// CHECK:STDOUT:   %Foo.ddeb7d.2: %Foo.type.a02530.2 = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .Foo = %Foo.decl.loc14
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %Foo.decl.loc14: %Foo.type.a02530.1 = fn_decl @Foo.1 [concrete = constants.%Foo.ddeb7d.1] {} {}
+// CHECK:STDOUT:   %Foo.decl.loc23: %Foo.type.a02530.2 = fn_decl @Foo.2 [concrete = constants.%Foo.ddeb7d.2] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Foo.1[<error>: <error>]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Foo.2[<error>: <error>]() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- todo_fail_raw_identifier.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 90 - 0
toolchain/check/testdata/interface/no_prelude/generic_vs_params.carbon

@@ -48,6 +48,26 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR:
 interface A(T: type) {}
 
+// --- fail_implicit_params_only_empty.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_implicit_params_only_empty.carbon:[[@LINE+4]]:14: error: expected explicit parameters after implicit parameters [GenericMissingExplicitParameters]
+// CHECK:STDERR: interface Bar[] {}
+// CHECK:STDERR:              ^~
+// CHECK:STDERR:
+interface Bar[] {}
+
+// --- fail_implicit_params_only.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_implicit_params_only.carbon:[[@LINE+4]]:14: error: expected explicit parameters after implicit parameters [GenericMissingExplicitParameters]
+// CHECK:STDERR: interface Bar[T:! type] {}
+// CHECK:STDERR:              ^~~~~~~~~~
+// CHECK:STDERR:
+interface Bar[T:! type] {}
+
 // CHECK:STDOUT: --- params.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -371,3 +391,73 @@ interface A(T: type) {}
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_implicit_params_only_empty.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar.type.982: type = generic_interface_type @Bar [concrete]
+// CHECK:STDOUT:   %Bar.generic: %Bar.type.982 = struct_value () [concrete]
+// CHECK:STDOUT:   %Bar.type.ea3: type = facet_type <@Bar> [concrete]
+// CHECK:STDOUT:   %Self: %Bar.type.ea3 = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Bar = %Bar.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: %Bar.type.982 = interface_decl @Bar [concrete = constants.%Bar.generic] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @Bar {
+// CHECK:STDOUT:   %Self: %Bar.type.ea3 = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_implicit_params_only.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %Bar.type.982: type = generic_interface_type @Bar [concrete]
+// CHECK:STDOUT:   %Bar.generic: %Bar.type.982 = struct_value () [concrete]
+// CHECK:STDOUT:   %Bar.type.7ed: type = facet_type <@Bar, @Bar(%T)> [symbolic]
+// CHECK:STDOUT:   %Self: %Bar.type.7ed = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Bar = %Bar.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: %Bar.type.982 = interface_decl @Bar [concrete = constants.%Bar.generic] {
+// CHECK:STDOUT:     %T.patt.loc8_15.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc8_15.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc8_15.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc8_15.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic interface @Bar(%T.loc8_15.1: type) {
+// CHECK:STDOUT:   %T.loc8_15.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc8_15.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc8_15.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc8_15.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Bar.type: type = facet_type <@Bar, @Bar(%T.loc8_15.2)> [symbolic = %Bar.type (constants.%Bar.type.7ed)]
+// CHECK:STDOUT:   %Self.2: %Bar.type.7ed = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   interface {
+// CHECK:STDOUT:     %Self.1: @Bar.%Bar.type (%Bar.type.7ed) = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:     witness = ()
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Bar(constants.%T) {
+// CHECK:STDOUT:   %T.loc8_15.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc8_15.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @Bar(%T.loc8_15.2) {}
+// CHECK:STDOUT:

+ 4 - 1
toolchain/diagnostics/diagnostic_kind.def

@@ -138,7 +138,6 @@ CARBON_DIAGNOSTIC_KIND(ExpectedDeclName)
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclNameAfterPeriod)
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclSemi)
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclSemiOrDefinition)
-CARBON_DIAGNOSTIC_KIND(ParamsRequiredAfterImplicit)
 CARBON_DIAGNOSTIC_KIND(ExpectedAfterBase)
 CARBON_DIAGNOSTIC_KIND(ExpectedBuiltinName)
 CARBON_DIAGNOSTIC_KIND(ImplExpectedAfterForall)
@@ -240,6 +239,7 @@ CARBON_DIAGNOSTIC_KIND(InvalidBuiltinSignature)
 CARBON_DIAGNOSTIC_KIND(DestroyFunctionOutsideClass)
 CARBON_DIAGNOSTIC_KIND(DestroyFunctionMissingSelf)
 CARBON_DIAGNOSTIC_KIND(DestroyFunctionUnexpectedImplicitParam)
+CARBON_DIAGNOSTIC_KIND(DestroyFunctionPositionalParams)
 CARBON_DIAGNOSTIC_KIND(DestroyFunctionNonEmptyExplicitParams)
 CARBON_DIAGNOSTIC_KIND(DestroyFunctionIncorrectReturnType)
 
@@ -465,6 +465,9 @@ CARBON_DIAGNOSTIC_KIND(RewriteForAssociatedFunction)
 // Facet type combination.
 CARBON_DIAGNOSTIC_KIND(FacetTypeRequiredForTypeAndOperator)
 
+// Generics.
+CARBON_DIAGNOSTIC_KIND(GenericMissingExplicitParameters)
+
 // Pattern matching diagnostics.
 CARBON_DIAGNOSTIC_KIND(TuplePatternSizeDoesntMatchLiteral)
 

+ 3 - 7
toolchain/parse/handle_decl_name_and_params.cpp

@@ -86,17 +86,13 @@ auto HandleDeclNameAndParams(Context& context) -> void {
 auto HandleDeclNameAndParamsAfterImplicit(Context& context) -> void {
   auto state = context.PopState();
 
+  state.state = State::DeclNameAndParamsAfterParams;
+  context.PushState(state);
+
   if (!context.PositionIs(Lex::TokenKind::OpenParen)) {
-    CARBON_DIAGNOSTIC(
-        ParamsRequiredAfterImplicit, Error,
-        "a `(` for parameters is required after implicit parameters");
-    context.emitter().Emit(*context.position(), ParamsRequiredAfterImplicit);
-    context.ReturnErrorOnState();
     return;
   }
 
-  state.state = State::DeclNameAndParamsAfterParams;
-  context.PushState(state);
   context.PushState(State::PatternListAsExplicit);
 }
 

+ 0 - 65
toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon

@@ -1,65 +0,0 @@
-// 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
-// TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon
-// TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon
-
-// CHECK:STDERR: fail_no_parens.carbon:[[@LINE+4]]:12: error: a `(` for parameters is required after implicit parameters [ParamsRequiredAfterImplicit]
-// CHECK:STDERR: class Foo[];
-// CHECK:STDERR:            ^
-// CHECK:STDERR:
-class Foo[];
-
-// CHECK:STDERR: fail_no_parens.carbon:[[@LINE+4]]:18: error: a `(` for parameters is required after implicit parameters [ParamsRequiredAfterImplicit]
-// CHECK:STDERR: class Foo[a: i32];
-// CHECK:STDERR:                  ^
-// CHECK:STDERR:
-class Foo[a: i32];
-
-// CHECK:STDERR: fail_no_parens.carbon:[[@LINE+4]]:17: error: a `(` for parameters is required after implicit parameters [ParamsRequiredAfterImplicit]
-// CHECK:STDERR: interface Bar[] {}
-// CHECK:STDERR:                 ^
-// CHECK:STDERR:
-interface Bar[] {}
-
-// CHECK:STDERR: fail_no_parens.carbon:[[@LINE+4]]:23: error: a `(` for parameters is required after implicit parameters [ParamsRequiredAfterImplicit]
-// CHECK:STDERR: interface Bar[a: i32] {}
-// CHECK:STDERR:                       ^
-// CHECK:STDERR:
-interface Bar[a: i32] {}
-
-// CHECK:STDOUT: - filename: fail_no_parens.carbon
-// CHECK:STDOUT:   parse_tree: [
-// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
-// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
-// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:         {kind: 'ImplicitParamListStart', text: '['},
-// CHECK:STDOUT:       {kind: 'ImplicitParamList', text: ']', subtree_size: 2},
-// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', has_error: yes, subtree_size: 5},
-// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
-// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'Foo'},
-// CHECK:STDOUT:         {kind: 'ImplicitParamListStart', text: '['},
-// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'a'},
-// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:         {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'ImplicitParamList', text: ']', subtree_size: 5},
-// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', has_error: yes, subtree_size: 8},
-// CHECK:STDOUT:       {kind: 'InterfaceIntroducer', text: 'interface'},
-// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'Bar'},
-// CHECK:STDOUT:         {kind: 'ImplicitParamListStart', text: '['},
-// CHECK:STDOUT:       {kind: 'ImplicitParamList', text: ']', subtree_size: 2},
-// CHECK:STDOUT:     {kind: 'InterfaceDecl', text: '}', has_error: yes, subtree_size: 5},
-// CHECK:STDOUT:       {kind: 'InterfaceIntroducer', text: 'interface'},
-// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'Bar'},
-// CHECK:STDOUT:         {kind: 'ImplicitParamListStart', text: '['},
-// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'a'},
-// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
-// CHECK:STDOUT:         {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
-// CHECK:STDOUT:       {kind: 'ImplicitParamList', text: ']', subtree_size: 5},
-// CHECK:STDOUT:     {kind: 'InterfaceDecl', text: '}', has_error: yes, subtree_size: 8},
-// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
-// CHECK:STDOUT:   ]

+ 79 - 0
toolchain/parse/testdata/generics/deduced_params/no_parens.carbon

@@ -0,0 +1,79 @@
+// 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
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/generics/deduced_params/no_parens.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/deduced_params/no_parens.carbon
+
+// --- class.carbon
+
+class Foo[];
+
+class Foo[a: i32];
+
+// --- interface.carbon
+
+interface Bar[] {}
+
+interface Bar[a: i32] {}
+
+// --- qualified.carbon
+
+interface Bar[].Baz[] {}
+
+// CHECK:STDOUT: - filename: class.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:       {kind: 'ImplicitParamList', text: ']', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'a'},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ImplicitParamList', text: ']', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: interface.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'InterfaceIntroducer', text: 'interface'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'Bar'},
+// CHECK:STDOUT:           {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'InterfaceDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'InterfaceDefinition', text: '}', subtree_size: 6},
+// CHECK:STDOUT:         {kind: 'InterfaceIntroducer', text: 'interface'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'Bar'},
+// CHECK:STDOUT:           {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'a'},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'InterfaceDefinitionStart', text: '{', subtree_size: 8},
+// CHECK:STDOUT:     {kind: 'InterfaceDefinition', text: '}', subtree_size: 9},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: qualified.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'InterfaceIntroducer', text: 'interface'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameBeforeParams', text: 'Bar'},
+// CHECK:STDOUT:             {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:           {kind: 'ImplicitParamList', text: ']', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'IdentifierNameQualifierWithParams', text: '.', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'Baz'},
+// CHECK:STDOUT:           {kind: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'InterfaceDefinitionStart', text: '{', subtree_size: 9},
+// CHECK:STDOUT:     {kind: 'InterfaceDefinition', text: '}', subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]