From 8738497301b8de85dfeb19be9df88b22b3e5cf06 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Mon, 17 Mar 2025 17:31:17 -0700 Subject: [PATCH] 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. --- toolchain/check/generic.cpp | 13 +++ toolchain/check/generic.h | 6 ++ toolchain/check/handle_class.cpp | 2 + toolchain/check/handle_function.cpp | 12 +-- toolchain/check/handle_interface.cpp | 2 + toolchain/check/name_component.cpp | 16 ++-- .../testdata/class/no_prelude/destroy.carbon | 31 +++++-- .../class/no_prelude/generic_vs_params.carbon | 68 ++++++++++++++ .../no_prelude/fail_todo_no_params.carbon | 34 +++++++ .../no_prelude/syntactic_merge.carbon | 62 +++++++++++++ .../no_prelude/generic_vs_params.carbon | 90 +++++++++++++++++++ toolchain/diagnostics/diagnostic_kind.def | 5 +- .../parse/handle_decl_name_and_params.cpp | 10 +-- .../deduced_params/fail_no_parens.carbon | 65 -------------- .../generics/deduced_params/no_parens.carbon | 79 ++++++++++++++++ 15 files changed, 402 insertions(+), 93 deletions(-) delete mode 100644 toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon create mode 100644 toolchain/parse/testdata/generics/deduced_params/no_parens.carbon diff --git a/toolchain/check/generic.cpp b/toolchain/check/generic.cpp index c819bfd62e19..376694d5c00a 100644 --- a/toolchain/check/generic.cpp +++ b/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 diff --git a/toolchain/check/generic.h b/toolchain/check/generic.h index ffda796251fd..a558d2090e5d 100644 --- a/toolchain/check/generic.h +++ b/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_ diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 97784ea55724..173c578e1901 100644 --- a/toolchain/check/handle_class.cpp +++ b/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()); diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 9edb4108cab9..cf623c7979fe 100644 --- a/toolchain/check/handle_function.cpp +++ b/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, diff --git a/toolchain/check/handle_interface.cpp b/toolchain/check/handle_interface.cpp index 9ec7b1007c53..a7390bf33200 100644 --- a/toolchain/check/handle_interface.cpp +++ b/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( diff --git a/toolchain/check/name_component.cpp b/toolchain/check/name_component.cpp index 078abb798309..df6136a1274c 100644 --- a/toolchain/check/name_component.cpp +++ b/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(); if (param_patterns_id) { first_param_node_id = context.node_stack() .PopForSoloNodeId(); - 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(); if (implicit_param_patterns_id) { @@ -37,9 +37,9 @@ auto PopNameComponent(Context& context, SemIR::InstId return_slot_pattern_id) context.node_stack() .PopForSoloNodeId(); // 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, diff --git a/toolchain/check/testdata/class/no_prelude/destroy.carbon b/toolchain/check/testdata/class/no_prelude/destroy.carbon index c4839c19ce3e..a56443c26b0a 100644 --- a/toolchain/check/testdata/class/no_prelude/destroy.carbon +++ b/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: = complete_type_witness %empty_struct_type [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: -// CHECK:STDOUT: file {} +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = 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: = 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 { diff --git a/toolchain/check/testdata/class/no_prelude/generic_vs_params.carbon b/toolchain/check/testdata/class/no_prelude/generic_vs_params.carbon index bd09e3fc0519..0432cc1f01f7 100644 --- a/toolchain/check/testdata/class/no_prelude/generic_vs_params.carbon +++ b/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 [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 [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: diff --git a/toolchain/check/testdata/function/declaration/no_prelude/fail_todo_no_params.carbon b/toolchain/check/testdata/function/declaration/no_prelude/fail_todo_no_params.carbon index 67a8bdca6850..9ab460674d0b 100644 --- a/toolchain/check/testdata/function/declaration/no_prelude/fail_todo_no_params.carbon +++ b/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 [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 {} diff --git a/toolchain/check/testdata/function/definition/no_prelude/syntactic_merge.carbon b/toolchain/check/testdata/function/definition/no_prelude/syntactic_merge.carbon index 16bd562582fd..f1d106ce6ca0 100644 --- a/toolchain/check/testdata/function/definition/no_prelude/syntactic_merge.carbon +++ b/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: = 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 [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: = 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[: ](); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Foo.2[: ]() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: --- todo_fail_raw_identifier.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/check/testdata/interface/no_prelude/generic_vs_params.carbon b/toolchain/check/testdata/interface/no_prelude/generic_vs_params.carbon index 36803319ea41..0a0ef5a17069 100644 --- a/toolchain/check/testdata/interface/no_prelude/generic_vs_params.carbon +++ b/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 [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 [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: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 21832a5d89cf..4781de1d59c1 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/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) diff --git a/toolchain/parse/handle_decl_name_and_params.cpp b/toolchain/parse/handle_decl_name_and_params.cpp index 6b5f191ad6ab..c559a75280af 100644 --- a/toolchain/parse/handle_decl_name_and_params.cpp +++ b/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); } diff --git a/toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon b/toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon deleted file mode 100644 index 8530e7812dbe..000000000000 --- a/toolchain/parse/testdata/generics/deduced_params/fail_no_parens.carbon +++ /dev/null @@ -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: ] diff --git a/toolchain/parse/testdata/generics/deduced_params/no_parens.carbon b/toolchain/parse/testdata/generics/deduced_params/no_parens.carbon new file mode 100644 index 000000000000..1a4881d35250 --- /dev/null +++ b/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: ]