diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index 4f4630e5eba6..6ba17d47b936 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -158,6 +158,15 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, return binding.pattern_id; }; + auto abstract_diagnoser = [&] { + CARBON_DIAGNOSTIC(AbstractTypeInVarPattern, Error, + "binding pattern has abstract type {0} in `var` " + "pattern", + SemIR::TypeId); + return context.emitter().Build(type_node, AbstractTypeInVarPattern, + cast_type_id); + }; + // A `self` binding can only appear in an implicit parameter list. if (name_id == SemIR::NameId::SelfValue && !context.node_stack().PeekIs(Parse::NodeKind::ImplicitParamListStart)) { @@ -192,6 +201,29 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, break; } + // Using `AsConcreteType` here causes `fn F[var self: Self]();` + // to fail since `Self` is an incomplete type. + if (node_kind == Parse::NodeKind::VarBindingPattern) { + auto [unqualified_type_id, qualifiers] = + context.types().GetUnqualifiedTypeAndQualifiers(cast_type_id); + if ((qualifiers & SemIR::TypeQualifiers::Partial) != + SemIR::TypeQualifiers::Partial && + context.types().Is(unqualified_type_id)) { + auto class_type = + context.types().GetAs(unqualified_type_id); + auto& class_info = context.classes().Get(class_type.class_id); + if (class_info.inheritance_kind == + SemIR::Class::InheritanceKind::Abstract) { + auto builder = abstract_diagnoser(); + auto direct_use = true; + NoteAbstractClass(context, class_type.class_id, direct_use, + builder); + builder.Emit(); + cast_type_id = SemIR::ErrorInst::TypeId; + } + } + } + auto result_inst_id = make_binding_pattern(); // A binding pattern in a function signature is a `Call` parameter @@ -228,16 +260,8 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, cast_type_inst_id); }; if (node_kind == Parse::NodeKind::VarBindingPattern) { - cast_type_id = AsConcreteType( - context, cast_type_id, type_node, incomplete_diagnoser, [&] { - CARBON_DIAGNOSTIC( - AbstractTypeInVarPattern, Error, - "binding pattern has abstract type {0} in `var` " - "pattern", - SemIR::TypeId); - return context.emitter().Build( - type_node, AbstractTypeInVarPattern, cast_type_id); - }); + cast_type_id = AsConcreteType(context, cast_type_id, type_node, + incomplete_diagnoser, abstract_diagnoser); } else { cast_type_id = AsCompleteType(context, cast_type_id, type_node, incomplete_diagnoser); diff --git a/toolchain/check/testdata/class/fail_abstract.carbon b/toolchain/check/testdata/class/fail_abstract.carbon index 77e57de824c2..e139dced8e60 100644 --- a/toolchain/check/testdata/class/fail_abstract.carbon +++ b/toolchain/check/testdata/class/fail_abstract.carbon @@ -48,6 +48,23 @@ fn Var() { var v: Abstract; } +// --- fail_abstract_var_function_param.carbon + +library "[[@TEST_NAME]]"; + +abstract class Abstract { +} + +// CHECK:STDERR: fail_abstract_var_function_param.carbon:[[@LINE+7]]:13: error: binding pattern has abstract type `Abstract` in `var` pattern [AbstractTypeInVarPattern] +// CHECK:STDERR: fn F(var p: Abstract) { +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: fail_abstract_var_function_param.carbon:[[@LINE-6]]:1: note: class was declared abstract here [ClassAbstractHere] +// CHECK:STDERR: abstract class Abstract { +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn F(var p: Abstract) { +} + // --- abstract_let.carbon library "[[@TEST_NAME]]"; @@ -291,6 +308,55 @@ fn CallReturnAbstract() { // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: --- fail_abstract_var_function_param.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Abstract: type = class_type @Abstract [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Abstract = %Abstract.decl +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %Abstract.decl: type = class_decl @Abstract [concrete = constants.%Abstract] {} {} +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %p.patt: = ref_binding_pattern p [concrete] +// CHECK:STDOUT: %p.param_patt: = var_param_pattern %p.patt, call_param0 [concrete] +// CHECK:STDOUT: %p.var_patt: = var_pattern %p.param_patt [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %p.param: ref = ref_param call_param0 +// CHECK:STDOUT: %Abstract.ref: type = name_ref Abstract, file.%Abstract.decl [concrete = constants.%Abstract] +// CHECK:STDOUT: %p: ref = ref_binding p, %p.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Abstract { +// CHECK:STDOUT: %complete_type: = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type] +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Abstract +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%p.param: ) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: --- abstract_let.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/check/testdata/class/partial.carbon b/toolchain/check/testdata/class/partial.carbon index 3ca89c5eca92..46d6129c865f 100644 --- a/toolchain/check/testdata/class/partial.carbon +++ b/toolchain/check/testdata/class/partial.carbon @@ -28,6 +28,15 @@ abstract class C { } fn A(p: partial C); //@dump-sem-ir-end +// --- abstract_var.carbon +library "[[@TEST_NAME]]"; + +abstract class C { } + +//@dump-sem-ir-begin +fn A(var p: partial C); +//@dump-sem-ir-end + // --- fail_partial_nondynamic.carbon library "[[@TEST_NAME]]"; @@ -224,6 +233,36 @@ fn F[T:! type](p: partial T*); // CHECK:STDOUT: // CHECK:STDOUT: fn @A(%p.param: %.43f); // CHECK:STDOUT: +// CHECK:STDOUT: --- abstract_var.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %.43f: type = partial_type %C [concrete] +// CHECK:STDOUT: %pattern_type: type = pattern_type %.43f [concrete] +// CHECK:STDOUT: %A.type: type = fn_type @A [concrete] +// CHECK:STDOUT: %A: %A.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %A.decl: %A.type = fn_decl @A [concrete = constants.%A] { +// CHECK:STDOUT: %p.patt: %pattern_type = ref_binding_pattern p [concrete] +// CHECK:STDOUT: %p.param_patt: %pattern_type = var_param_pattern %p.patt, call_param0 [concrete] +// CHECK:STDOUT: %p.var_patt: %pattern_type = var_pattern %p.param_patt [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %p.param: ref %.43f = ref_param call_param0 +// CHECK:STDOUT: %.loc6_13.1: type = splice_block %.loc6_13.2 [concrete = constants.%.43f] { +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %.loc6_13.2: type = partial_type %C.ref [concrete = constants.%.43f] +// CHECK:STDOUT: } +// CHECK:STDOUT: %p: ref %.43f = ref_binding p, %p.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @A(%p.param: %.43f); +// CHECK:STDOUT: // CHECK:STDOUT: --- fail_partial_nondynamic.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/check/type_completion.cpp b/toolchain/check/type_completion.cpp index 5c86bf4ddf3a..56e40686fc44 100644 --- a/toolchain/check/type_completion.cpp +++ b/toolchain/check/type_completion.cpp @@ -54,6 +54,20 @@ auto NoteIncompleteInterface(Context& context, SemIR::InterfaceId interface_id, } } +auto NoteAbstractClass(Context& context, SemIR::ClassId class_id, + bool direct_use, DiagnosticBuilder& builder) -> void { + const auto& class_info = context.classes().Get(class_id); + CARBON_CHECK( + class_info.inheritance_kind == SemIR::Class::InheritanceKind::Abstract, + "Class is not abstract"); + CARBON_DIAGNOSTIC( + ClassAbstractHere, Note, + "{0:=0:uses class that|=1:class} was declared abstract here", + Diagnostics::IntAsSelect); + builder.Note(class_info.definition_id, ClassAbstractHere, + static_cast(direct_use)); +} + static auto NoteIncompleteNamedConstraint( Context& context, SemIR::NamedConstraintId named_constraint_id, DiagnosticBuilder& builder) -> void { @@ -728,22 +742,6 @@ auto RequireCompleteType(Context& context, SemIR::TypeId type_id, return true; } -// Adds a note to a diagnostic explaining that a class is abstract. -static auto NoteAbstractClass(Context& context, SemIR::ClassId class_id, - bool direct_use, DiagnosticBuilder& builder) - -> void { - const auto& class_info = context.classes().Get(class_id); - CARBON_CHECK( - class_info.inheritance_kind == SemIR::Class::InheritanceKind::Abstract, - "Class is not abstract"); - CARBON_DIAGNOSTIC( - ClassAbstractHere, Note, - "{0:=0:uses class that|=1:class} was declared abstract here", - Diagnostics::IntAsSelect); - builder.Note(class_info.definition_id, ClassAbstractHere, - static_cast(direct_use)); -} - auto RequireConcreteType(Context& context, SemIR::TypeId type_id, SemIR::LocId loc_id, MakeDiagnosticBuilderFn diagnoser, MakeDiagnosticBuilderFn abstract_diagnoser) -> bool { diff --git a/toolchain/check/type_completion.h b/toolchain/check/type_completion.h index 6f9324b5873b..312f25e6f968 100644 --- a/toolchain/check/type_completion.h +++ b/toolchain/check/type_completion.h @@ -85,6 +85,10 @@ auto NoteIncompleteClass(Context& context, SemIR::ClassId class_id, auto NoteIncompleteInterface(Context& context, SemIR::InterfaceId interface_id, DiagnosticBuilder& builder) -> void; +// Adds a note to a diagnostic explaining that a class is abstract. +auto NoteAbstractClass(Context& context, SemIR::ClassId class_id, + bool direct_use, DiagnosticBuilder& builder) -> void; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_TYPE_COMPLETION_H_