From cd1ecf1297808623b3aff1c4d105c058a4ee5724 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 5 Dec 2024 19:13:05 -0800 Subject: [PATCH] When a builtin function expects type T also allow an adapter for T. (#4643) Extends the set of function signatures that support being given a builtin definition to include cases where a parameter or return type is an adapter for a supported type. For example, if we can give a builtin definition to `Add(a: i32, b: i32) -> i32`, then we can also give a builtin definition to `Add(a: MyI32, b: MyI32) - >MyI32` where `MyI32` adapts `i32`. This is a prerequisite for changing `Core.Int` to be a class type that adapts the builtin int type. --- toolchain/check/handle_function.cpp | 37 ++- .../builtin/no_prelude/adapted_type.carbon | 258 ++++++++++++++++++ toolchain/sem_ir/builtin_function_kind.cpp | 30 +- 3 files changed, 307 insertions(+), 18 deletions(-) create mode 100644 toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index f278d9dafaea9..960f4a86865ab 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -328,21 +328,9 @@ auto HandleParseNode(Context& context, Parse::FunctionDeclId node_id) -> bool { return true; } -// Processes a function definition after a signature for which we have already -// built a function ID. This logic is shared between processing regular function -// definitions and delayed parsing of inline method definitions. -static auto HandleFunctionDefinitionAfterSignature( - Context& context, Parse::FunctionDefinitionStartId node_id, - SemIR::FunctionId function_id, SemIR::InstId decl_id) -> void { - auto& function = context.functions().Get(function_id); - - // Create the function scope and the entry block. - context.return_scope_stack().push_back({.decl_id = decl_id}); - context.inst_block_stack().Push(); - context.scope_stack().Push(decl_id); - StartGenericDefinition(context); - context.AddCurrentCodeBlockToFunction(); - +static auto CheckFunctionDefinitionSignature(Context& context, + SemIR::Function& function) + -> void { // Check the return type is complete. CheckFunctionReturnType(context, function.return_slot_pattern_id, function, SemIR::SpecificId::Invalid); @@ -370,6 +358,24 @@ static auto HandleFunctionDefinitionAfterSignature( param_ref_id, IncompleteTypeInFunctionParam, param_ref_id); }); } +} + +// Processes a function definition after a signature for which we have already +// built a function ID. This logic is shared between processing regular function +// definitions and delayed parsing of inline method definitions. +static auto HandleFunctionDefinitionAfterSignature( + Context& context, Parse::FunctionDefinitionStartId node_id, + SemIR::FunctionId function_id, SemIR::InstId decl_id) -> void { + auto& function = context.functions().Get(function_id); + + // Create the function scope and the entry block. + context.return_scope_stack().push_back({.decl_id = decl_id}); + context.inst_block_stack().Push(); + context.scope_stack().Push(decl_id); + StartGenericDefinition(context); + context.AddCurrentCodeBlockToFunction(); + + CheckFunctionDefinitionSignature(context, function); context.node_stack().Push(node_id, function_id); } @@ -508,6 +514,7 @@ auto HandleParseNode(Context& context, auto builtin_kind = LookupBuiltinFunctionKind(context, name_id); if (builtin_kind != SemIR::BuiltinFunctionKind::None) { auto& function = context.functions().Get(function_id); + CheckFunctionDefinitionSignature(context, function); if (IsValidBuiltinDeclaration(context, function, builtin_kind)) { function.builtin_function_kind = builtin_kind; // Build an empty generic definition if this is a generic builtin. diff --git a/toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon b/toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon new file mode 100644 index 0000000000000..2a37adfecceea --- /dev/null +++ b/toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon @@ -0,0 +1,258 @@ +// 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/check/testdata/function/builtin/no_prelude/adapted_type.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/builtin/no_prelude/adapted_type.carbon + +// --- adapt.carbon + +library "[[@TEST_NAME]]"; + +fn IntLiteral() -> type = "int_literal.make_type"; + +class MyIntLiteral { + adapt IntLiteral(); +} + +fn Int(N: MyIntLiteral) -> type = "int.make_type_signed"; + +class MyInt32 { + adapt Int(32 as MyIntLiteral); + + fn Make(a: MyIntLiteral) -> MyInt32; +} + +fn MyInt32.Make(a: MyIntLiteral) -> MyInt32 = "int.convert_checked"; + +fn MyAdd(a: MyInt32, b: MyInt32) -> MyInt32 = "int.sadd"; + +var v: MyInt32 = MyAdd(MyInt32.Make(1 as MyIntLiteral), MyInt32.Make(2 as MyIntLiteral)); + +// --- fail_bad_adapt.carbon + +library "[[@TEST_NAME]]"; + +class MyIntLiteral { + adapt {}; +} + +// CHECK:STDERR: fail_bad_adapt.carbon:[[@LINE+3]]:1: error: invalid signature for builtin function "int.make_type_signed" [InvalidBuiltinSignature] +// CHECK:STDERR: fn Int(N: MyIntLiteral) -> type = "int.make_type_signed"; +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +fn Int(N: MyIntLiteral) -> type = "int.make_type_signed"; + +// CHECK:STDOUT: --- adapt.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %IntLiteral.type: type = fn_type @IntLiteral [template] +// CHECK:STDOUT: %IntLiteral: %IntLiteral.type = struct_value () [template] +// CHECK:STDOUT: %MyIntLiteral: type = class_type @MyIntLiteral [template] +// CHECK:STDOUT: %complete_type.1: = complete_type_witness Core.IntLiteral [template] +// CHECK:STDOUT: %Int.type: type = fn_type @Int [template] +// CHECK:STDOUT: %Int: %Int.type = struct_value () [template] +// CHECK:STDOUT: %MyInt32: type = class_type @MyInt32 [template] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template] +// CHECK:STDOUT: %i32: type = int_type signed, %int_32 [template] +// CHECK:STDOUT: %Make.type: type = fn_type @Make [template] +// CHECK:STDOUT: %Make: %Make.type = struct_value () [template] +// CHECK:STDOUT: %complete_type.2: = complete_type_witness %i32 [template] +// CHECK:STDOUT: %MyAdd.type: type = fn_type @MyAdd [template] +// CHECK:STDOUT: %MyAdd: %MyAdd.type = struct_value () [template] +// CHECK:STDOUT: %int_1.1: Core.IntLiteral = int_value 1 [template] +// CHECK:STDOUT: %int_1.2: %MyInt32 = int_value 1 [template] +// CHECK:STDOUT: %int_2.1: Core.IntLiteral = int_value 2 [template] +// CHECK:STDOUT: %int_2.2: %MyInt32 = int_value 2 [template] +// CHECK:STDOUT: %int_3: %MyInt32 = int_value 3 [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .IntLiteral = %IntLiteral.decl +// CHECK:STDOUT: .MyIntLiteral = %MyIntLiteral.decl +// CHECK:STDOUT: .Int = %Int.decl +// CHECK:STDOUT: .MyInt32 = %MyInt32.decl +// CHECK:STDOUT: .MyAdd = %MyAdd.decl +// CHECK:STDOUT: .v = %v +// CHECK:STDOUT: } +// CHECK:STDOUT: %IntLiteral.decl: %IntLiteral.type = fn_decl @IntLiteral [template = constants.%IntLiteral] { +// CHECK:STDOUT: %return.patt: type = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: type = out_param_pattern %return.patt, runtime_param0 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %return.param: ref type = out_param runtime_param0 +// CHECK:STDOUT: %return: ref type = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %MyIntLiteral.decl: type = class_decl @MyIntLiteral [template = constants.%MyIntLiteral] {} {} +// CHECK:STDOUT: %Int.decl: %Int.type = fn_decl @Int [template = constants.%Int] { +// CHECK:STDOUT: %N.patt: %MyIntLiteral = binding_pattern N +// CHECK:STDOUT: %N.param_patt: %MyIntLiteral = value_param_pattern %N.patt, runtime_param0 +// CHECK:STDOUT: %return.patt: type = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: type = out_param_pattern %return.patt, runtime_param1 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %MyIntLiteral.ref: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %N.param: %MyIntLiteral = value_param runtime_param0 +// CHECK:STDOUT: %N: %MyIntLiteral = bind_name N, %N.param +// CHECK:STDOUT: %return.param: ref type = out_param runtime_param1 +// CHECK:STDOUT: %return: ref type = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %MyInt32.decl: type = class_decl @MyInt32 [template = constants.%MyInt32] {} {} +// CHECK:STDOUT: %Make.decl: %Make.type = fn_decl @Make [template = constants.%Make] { +// CHECK:STDOUT: %a.patt: %MyIntLiteral = binding_pattern a +// CHECK:STDOUT: %a.param_patt: %MyIntLiteral = value_param_pattern %a.patt, runtime_param0 +// CHECK:STDOUT: %return.patt: %MyInt32 = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: %MyInt32 = out_param_pattern %return.patt, runtime_param1 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %MyIntLiteral.ref.loc18: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %MyInt32.ref.loc18: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %a.param.loc18: %MyIntLiteral = value_param runtime_param0 +// CHECK:STDOUT: %a.loc18: %MyIntLiteral = bind_name a, %a.param.loc18 +// CHECK:STDOUT: %return.param.loc18: ref %MyInt32 = out_param runtime_param1 +// CHECK:STDOUT: %return.loc18: ref %MyInt32 = return_slot %return.param.loc18 +// CHECK:STDOUT: } +// CHECK:STDOUT: %MyAdd.decl: %MyAdd.type = fn_decl @MyAdd [template = constants.%MyAdd] { +// CHECK:STDOUT: %a.patt: %MyInt32 = binding_pattern a +// CHECK:STDOUT: %a.param_patt: %MyInt32 = value_param_pattern %a.patt, runtime_param0 +// CHECK:STDOUT: %b.patt: %MyInt32 = binding_pattern b +// CHECK:STDOUT: %b.param_patt: %MyInt32 = value_param_pattern %b.patt, runtime_param1 +// CHECK:STDOUT: %return.patt: %MyInt32 = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: %MyInt32 = out_param_pattern %return.patt, runtime_param2 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %MyInt32.ref.loc20_13: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %MyInt32.ref.loc20_25: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %MyInt32.ref.loc20_37: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %a.param: %MyInt32 = value_param runtime_param0 +// CHECK:STDOUT: %a: %MyInt32 = bind_name a, %a.param +// CHECK:STDOUT: %b.param: %MyInt32 = value_param runtime_param1 +// CHECK:STDOUT: %b: %MyInt32 = bind_name b, %b.param +// CHECK:STDOUT: %return.param: ref %MyInt32 = out_param runtime_param2 +// CHECK:STDOUT: %return: ref %MyInt32 = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %MyInt32.ref: type = name_ref MyInt32, %MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %v.var: ref %MyInt32 = var v +// CHECK:STDOUT: %v: ref %MyInt32 = bind_name v, %v.var +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @MyIntLiteral { +// CHECK:STDOUT: %IntLiteral.ref: %IntLiteral.type = name_ref IntLiteral, file.%IntLiteral.decl [template = constants.%IntLiteral] +// CHECK:STDOUT: %int_literal.make_type: init type = call %IntLiteral.ref() [template = Core.IntLiteral] +// CHECK:STDOUT: %.loc7_21.1: type = value_of_initializer %int_literal.make_type [template = Core.IntLiteral] +// CHECK:STDOUT: %.loc7_21.2: type = converted %int_literal.make_type, %.loc7_21.1 [template = Core.IntLiteral] +// CHECK:STDOUT: adapt_decl %.loc7_21.2 [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness Core.IntLiteral [template = constants.%complete_type.1] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%MyIntLiteral +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @MyInt32 { +// CHECK:STDOUT: %Int.ref: %Int.type = name_ref Int, file.%Int.decl [template = constants.%Int] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [template = constants.%int_32] +// CHECK:STDOUT: %MyIntLiteral.ref: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %.loc13_16.1: %MyIntLiteral = as_compatible %int_32 [template = constants.%int_32] +// CHECK:STDOUT: %.loc13_16.2: %MyIntLiteral = converted %int_32, %.loc13_16.1 [template = constants.%int_32] +// CHECK:STDOUT: %int.make_type_signed: init type = call %Int.ref(%.loc13_16.2) [template = constants.%i32] +// CHECK:STDOUT: %.loc13_32.1: type = value_of_initializer %int.make_type_signed [template = constants.%i32] +// CHECK:STDOUT: %.loc13_32.2: type = converted %int.make_type_signed, %.loc13_32.1 [template = constants.%i32] +// CHECK:STDOUT: adapt_decl %.loc13_32.2 [template] +// CHECK:STDOUT: %Make.decl: %Make.type = fn_decl @Make [template = constants.%Make] { +// CHECK:STDOUT: %a.patt: %MyIntLiteral = binding_pattern a +// CHECK:STDOUT: %a.param_patt: %MyIntLiteral = value_param_pattern %a.patt, runtime_param0 +// CHECK:STDOUT: %return.patt: %MyInt32 = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: %MyInt32 = out_param_pattern %return.patt, runtime_param1 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %MyIntLiteral.ref.loc15: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %MyInt32.ref.loc15: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %a.param.loc15: %MyIntLiteral = value_param runtime_param0 +// CHECK:STDOUT: %a.loc15: %MyIntLiteral = bind_name a, %a.param.loc15 +// CHECK:STDOUT: %return.param.loc15: ref %MyInt32 = out_param runtime_param1 +// CHECK:STDOUT: %return.loc15: ref %MyInt32 = return_slot %return.param.loc15 +// CHECK:STDOUT: } +// CHECK:STDOUT: %complete_type: = complete_type_witness %i32 [template = constants.%complete_type.2] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%MyInt32 +// CHECK:STDOUT: .Make = %Make.decl +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @IntLiteral() -> type = "int_literal.make_type"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Int(%N.param_patt: %MyIntLiteral) -> type = "int.make_type_signed"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Make(%a.param_patt: %MyIntLiteral) -> %MyInt32 = "int.convert_checked"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @MyAdd(%a.param_patt: %MyInt32, %b.param_patt: %MyInt32) -> %MyInt32 = "int.sadd"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %MyAdd.ref: %MyAdd.type = name_ref MyAdd, file.%MyAdd.decl [template = constants.%MyAdd] +// CHECK:STDOUT: %MyInt32.ref.loc22_24: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %Make.ref.loc22_31: %Make.type = name_ref Make, @MyInt32.%Make.decl [template = constants.%Make] +// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1.1] +// CHECK:STDOUT: %MyIntLiteral.ref.loc22_42: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %.loc22_39.1: %MyIntLiteral = as_compatible %int_1 [template = constants.%int_1.1] +// CHECK:STDOUT: %.loc22_39.2: %MyIntLiteral = converted %int_1, %.loc22_39.1 [template = constants.%int_1.1] +// CHECK:STDOUT: %int.convert_checked.loc22_54: init %MyInt32 = call %Make.ref.loc22_31(%.loc22_39.2) [template = constants.%int_1.2] +// CHECK:STDOUT: %MyInt32.ref.loc22_57: type = name_ref MyInt32, file.%MyInt32.decl [template = constants.%MyInt32] +// CHECK:STDOUT: %Make.ref.loc22_64: %Make.type = name_ref Make, @MyInt32.%Make.decl [template = constants.%Make] +// CHECK:STDOUT: %int_2: Core.IntLiteral = int_value 2 [template = constants.%int_2.1] +// CHECK:STDOUT: %MyIntLiteral.ref.loc22_75: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %.loc22_72.1: %MyIntLiteral = as_compatible %int_2 [template = constants.%int_2.1] +// CHECK:STDOUT: %.loc22_72.2: %MyIntLiteral = converted %int_2, %.loc22_72.1 [template = constants.%int_2.1] +// CHECK:STDOUT: %int.convert_checked.loc22_87: init %MyInt32 = call %Make.ref.loc22_64(%.loc22_72.2) [template = constants.%int_2.2] +// CHECK:STDOUT: %.loc22_54.1: %MyInt32 = value_of_initializer %int.convert_checked.loc22_54 [template = constants.%int_1.2] +// CHECK:STDOUT: %.loc22_54.2: %MyInt32 = converted %int.convert_checked.loc22_54, %.loc22_54.1 [template = constants.%int_1.2] +// CHECK:STDOUT: %.loc22_87.1: %MyInt32 = value_of_initializer %int.convert_checked.loc22_87 [template = constants.%int_2.2] +// CHECK:STDOUT: %.loc22_87.2: %MyInt32 = converted %int.convert_checked.loc22_87, %.loc22_87.1 [template = constants.%int_2.2] +// CHECK:STDOUT: %int.sadd: init %MyInt32 = call %MyAdd.ref(%.loc22_54.2, %.loc22_87.2) [template = constants.%int_3] +// CHECK:STDOUT: assign file.%v.var, %int.sadd +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_bad_adapt.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %MyIntLiteral: type = class_type @MyIntLiteral [template] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template] +// CHECK:STDOUT: %Int.type: type = fn_type @Int [template] +// CHECK:STDOUT: %Int: %Int.type = struct_value () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .MyIntLiteral = %MyIntLiteral.decl +// CHECK:STDOUT: .Int = %Int.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %MyIntLiteral.decl: type = class_decl @MyIntLiteral [template = constants.%MyIntLiteral] {} {} +// CHECK:STDOUT: %Int.decl: %Int.type = fn_decl @Int [template = constants.%Int] { +// CHECK:STDOUT: %N.patt: %MyIntLiteral = binding_pattern N +// CHECK:STDOUT: %N.param_patt: %MyIntLiteral = value_param_pattern %N.patt, runtime_param0 +// CHECK:STDOUT: %return.patt: type = return_slot_pattern +// CHECK:STDOUT: %return.param_patt: type = out_param_pattern %return.patt, runtime_param1 +// CHECK:STDOUT: } { +// CHECK:STDOUT: %MyIntLiteral.ref: type = name_ref MyIntLiteral, file.%MyIntLiteral.decl [template = constants.%MyIntLiteral] +// CHECK:STDOUT: %N.param: %MyIntLiteral = value_param runtime_param0 +// CHECK:STDOUT: %N: %MyIntLiteral = bind_name N, %N.param +// CHECK:STDOUT: %return.param: ref type = out_param runtime_param1 +// CHECK:STDOUT: %return: ref type = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @MyIntLiteral { +// CHECK:STDOUT: %.loc5_10: %empty_struct_type = struct_literal () +// CHECK:STDOUT: %.loc5_11: type = converted %.loc5_10, constants.%empty_struct_type [template = constants.%empty_struct_type] +// CHECK:STDOUT: adapt_decl %.loc5_11 [template] +// CHECK:STDOUT: %complete_type: = complete_type_witness %empty_struct_type [template = constants.%complete_type] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%MyIntLiteral +// CHECK:STDOUT: complete_type_witness = %complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Int(%N.param_patt: %MyIntLiteral) -> type; +// CHECK:STDOUT: diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index 6f0eed3602b8c..2f60dc1546759 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/toolchain/sem_ir/builtin_function_kind.cpp @@ -45,8 +45,11 @@ struct TypeParam { if (state.type_params[I].is_valid() && type_id != state.type_params[I]) { return false; } + if (!TypeConstraint::Check(sem_ir, state, type_id)) { + return false; + } state.type_params[I] = type_id; - return TypeConstraint::Check(sem_ir, state, type_id); + return true; } }; @@ -99,6 +102,27 @@ struct AnyFloat { } }; +// Checks that the specified type matches the given type constraint. +template +auto Check(const File& sem_ir, ValidateState& state, TypeId type_id) -> bool { + while (type_id.is_valid()) { + // Allow a type that satisfies the constraint. + if (TypeConstraint::Check(sem_ir, state, type_id)) { + return true; + } + + // Also allow a class type that adapts a matching type. + auto class_type = sem_ir.types().TryGetAs(type_id); + if (!class_type) { + break; + } + type_id = sem_ir.classes() + .Get(class_type->class_id) + .GetAdaptedType(sem_ir, class_type->specific_id); + } + return false; +} + // Constraint that requires the type to be the type type. using Type = BuiltinType; @@ -136,7 +160,7 @@ static auto ValidateSignature(const File& sem_ir, // Argument types must match. if (![&](std::index_sequence) { - return ((SignatureTraits::template arg_t::Check( + return ((Check>( sem_ir, state, arg_types[Indexes])) && ...); }(std::make_index_sequence())) { @@ -144,7 +168,7 @@ static auto ValidateSignature(const File& sem_ir, } // Result type must match. - if (!SignatureTraits::result_t::Check(sem_ir, state, return_type)) { + if (!Check(sem_ir, state, return_type)) { return false; }