diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 0f5b2c2f7f60..c67fb28cac8a 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -315,7 +315,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id, prev_impl.definition_id = impl_decl_id; } - if (auto match_first = context.match_first_context()) { + if (auto& match_first = context.match_first_context()) { if (prev_impl.match_first_id.has_value()) { if (!impl_had_error) { CARBON_DIAGNOSTIC( @@ -372,7 +372,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id, context.generics().GetSelfSpecific(impl.generic_id)); impl.witness_block_id = context.inst_block_stack().Pop(); - if (auto match_first = context.match_first_context()) { + if (auto& match_first = context.match_first_context()) { impl.match_first_id = match_first->decl_id; impl.decl_loc_in_match_first = SemIR::LocId(impl_decl_id); impl.match_first_position = match_first->block_size; diff --git a/toolchain/check/impl_lookup.cpp b/toolchain/check/impl_lookup.cpp index 2298c53a7c8d..94b51de76472 100644 --- a/toolchain/check/impl_lookup.cpp +++ b/toolchain/check/impl_lookup.cpp @@ -662,6 +662,8 @@ struct CandidateImpl { // Used for sorting the candidates to find the most-specialized match. TypeStructure type_structure; + SemIR::InstId match_first_block; + int match_first_position; }; struct CandidateImpls { @@ -709,8 +711,16 @@ static auto CollectCandidateImplsForQuery( continue; } - if (final_only && !TreatImplAsFinal(context, impl)) { - continue; + if (final_only) { + if (!TreatImplAsFinal(context, impl) && + // TODO: For `match_first_is_final`, the impl can only be treated as + // final if there's no `impl` above it in the match_first block that + // overlaps with the query (such that a more specific query might + // choose it). See + // https://github.com/carbon-language/carbon-lang/blob/de8b03faa3178ae683d8e7124fbcba81eb88e00c/proposals/p005337-interface-extension-and-final-impl-update.md#using-associated-constants-from-impls-in-a-final-match_first + !impl.match_first_is_final) { + continue; + } } if (llvm::is_contained(context.forbidden_impls(), id)) { @@ -746,17 +756,24 @@ static auto CollectCandidateImplsForQuery( continue; } - candidates.impls.push_back({id, &impl, std::move(*type_structure)}); + candidates.impls.push_back({id, &impl, std::move(*type_structure), + impl.match_first_id, + impl.match_first_position}); } auto compare = [](auto& lhs, auto& rhs) -> bool { + // If they are in the same block, then order wins. Final impls will always + // be in the same block if they overlap, as will impls that have the same + // type structure. + if (lhs.match_first_block.has_value() && + lhs.match_first_block == rhs.match_first_block) { + return lhs.match_first_position < rhs.match_first_position; + } + // Otherwise, specificity wins. return lhs.type_structure < rhs.type_structure; }; // Stable sort is used so that impls that are seen first are preferred when // they have an equal priority ordering. - // TODO: Allow Carbon code to provide a priority ordering explicitly. For - // now they have all the same priority, so the priority is the order in - // which they are found in code. llvm::stable_sort(candidates.impls, compare); return candidates; diff --git a/toolchain/check/impl_validation.cpp b/toolchain/check/impl_validation.cpp index 6b8b307396ec..6d5c0b881ec9 100644 --- a/toolchain/check/impl_validation.cpp +++ b/toolchain/check/impl_validation.cpp @@ -31,6 +31,7 @@ struct ImplInfo { SemIR::TypeInstId self_id; SemIR::InstId latest_decl_id; SemIR::SpecificInterface interface; + SemIR::InstId match_first_id; // Whether the `impl` decl was imported or from the local file. bool is_local; // If imported, the IR from which the `impl` decl was imported. @@ -90,6 +91,7 @@ static auto GetImplInfo(Context& context, SemIR::ImplId impl_id) -> ImplInfo { .self_id = impl.self_id, .latest_decl_id = impl.latest_decl_id(), .interface = impl.interface, + .match_first_id = impl.match_first_id, .is_local = !ir_id.has_value(), .ir_id = ir_id, .library_id = library_id, @@ -236,14 +238,16 @@ static auto DiagnoseOrphanImpl(Context& context, const ImplInfo& impl, } // The type structure each non-final `impl` must differ from all other non-final -// `impl` for the same interface visible from the file. +// `impl` for the same interface visible from the file. However, if the `impl`s +// are associated with the same match_first block, then they are allowed to have +// the same type structure. // // Returns true if an error was diagnosed. -static auto DiagnoseNonFinalImplsWithSameTypeStructure(Context& context, - const ImplInfo& impl_a, - const ImplInfo& impl_b) - -> bool { - if (impl_a.type_structure == impl_b.type_structure) { +static auto DiagnoseNonFinalImplsWithSameTypeStructureOutsideMatchFirst( + Context& context, const ImplInfo& impl_a, const ImplInfo& impl_b) -> bool { + if (impl_a.type_structure == impl_b.type_structure && + !(impl_a.match_first_id.has_value() && + impl_a.match_first_id == impl_b.match_first_id)) { CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructure, Error, "found non-final `impl` with the same type " "structure as another non-final `impl`"); @@ -330,15 +334,14 @@ static auto DiagnoseFinalImplsOverlapInDifferentFiles(Context& context, // Two final impls in the same file can not overlap in their type // structure if they are not in the same match_first block. // -// TODO: Support for match_first needed here when they exist in the -// toolchain. -// // Returns true if an error was diagnosed. static auto DiagnoseFinalImplsOverlapOutsideMatchFirst(Context& context, const ImplInfo& impl_a, const ImplInfo& impl_b) -> bool { - if (impl_a.is_local && impl_b.is_local) { + if (impl_a.is_local && impl_b.is_local && + !(impl_a.match_first_id.has_value() && + impl_a.match_first_id == impl_b.match_first_id)) { CARBON_DIAGNOSTIC(FinalImplOverlapsSameFile, Error, "`final impl` overlaps with `final impl` from same file " "outside a `match_first` block"); @@ -417,8 +420,8 @@ static auto ValidateImplsForInterface(Context& context, // but possibly different files, if one is in the api and one in the // impl file. if (impl_a.library_id == impl_b.library_id) { - if (DiagnoseNonFinalImplsWithSameTypeStructure(context, impl_a, - impl_b)) { + if (DiagnoseNonFinalImplsWithSameTypeStructureOutsideMatchFirst( + context, impl_a, impl_b)) { // The same final `impl_a` may overlap with multiple `impl_b`s, // and we want to diagnose each `impl_b`. did_diagnose_non_final_impls_with_same_type_structure = true; diff --git a/toolchain/check/testdata/match_first/prioritization.carbon b/toolchain/check/testdata/match_first/prioritization.carbon new file mode 100644 index 000000000000..47806ab41895 --- /dev/null +++ b/toolchain/check/testdata/match_first/prioritization.carbon @@ -0,0 +1,357 @@ +// 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 +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon +// ^ARGS: -v compile %s +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/match_first/prioritization.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/match_first/prioritization.carbon + +// --- overlap_allowed_in_match_first.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + impl forall [T: Y] T as Z where .Z1 = {.y: type}; + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +// --- fail_overlap_not_allowed_in_different_match_first.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +// CHECK:STDERR: fail_overlap_not_allowed_in_different_match_first.carbon:[[@LINE+7]]:1: error: found non-final `impl` with the same type structure as another non-final `impl` [ImplNonFinalSameTypeStructure] +// CHECK:STDERR: impl forall [T: X] T as Z where .Z1 = {.x: type} {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_overlap_not_allowed_in_different_match_first.carbon:[[@LINE-4]]:1: note: other `impl` here [ImplNonFinalSameTypeStructureNote] +// CHECK:STDERR: impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + impl forall [T: Y] T as Z where .Z1 = {.y: type}; +} +match_first { + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +// --- lookup_overlapping_impl.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + impl forall [T: Y] T as Z where .Z1 = {.y: type}; + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +fn F() { + class FC; + impl FC as Y {} + // Since GC impls Y and not X, the .y impl should be selected. + {.y = type} as FC.(Z.Z1); +} + +fn G() { + class GC; + impl GC as X {} + // Since GC impls X and not Y, the .x impl should be selected. + {.x = type} as GC.(Z.Z1); +} + +// --- prioritization_first_declarared.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + // The match_first block is in the same order that the impls are introduced. + // This makes us prioritize the first introduced impl. + impl forall [T: Y] T as Z where .Z1 = {.y: type}; + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +fn F() { + class C; + impl C as Y {} + impl C as X {} + // Since C impls X and Y, the first one in the match_first block + // should be selected, which is the .y impl. + {.y = type} as C.(Z.Z1); +} + +// --- prioritization_second_declarared.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + // The match_first block is in the opposite order that the impls are + // introduced. This makes us prioritize the second introduced impl. + impl forall [T: X] T as Z where .Z1 = {.x: type}; + impl forall [T: Y] T as Z where .Z1 = {.y: type}; +} + +fn F() { + class C; + impl C as Y {} + impl C as X {} + // Since C impls X and Y, the first one in the match_first block + // should be selected, which is the .x impl. + {.x = type} as C.(Z.Z1); +} + +// --- fail_prioritization_does_not_choose_second_declarared.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + // The match_first block is in the same order that the impls are introduced. + // This makes us prioritize the first introduced impl. + impl forall [T: Y] T as Z where .Z1 = {.y: type}; + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +fn F() { + class C; + impl C as Y {} + impl C as X {} + // Since C impls X and Y, the first one in the match_first block + // should be selected, which is the .y impl. So this should fail. + // CHECK:STDERR: fail_prioritization_does_not_choose_second_declarared.carbon:[[@LINE+4]]:3: error: struct `{.y: type}` has no field named `x` [StructInitUnexpectedFieldInLiteral] + // CHECK:STDERR: {.x = type} as C.(Z.Z1); + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: + {.x = type} as C.(Z.Z1); +} + +// --- fail_prioritization_does_not_choose_first_declarared.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + // The match_first block is in the opposite order that the impls are + // introduced. This makes us prioritize the second introduced impl. + impl forall [T: X] T as Z where .Z1 = {.x: type}; + impl forall [T: Y] T as Z where .Z1 = {.y: type}; +} + +fn F() { + class C; + impl C as Y {} + impl C as X {} + // Since C impls X and Y, the first one in the match_first block + // should be selected, which is the .x impl. So this should fail. + // CHECK:STDERR: fail_prioritization_does_not_choose_first_declarared.carbon:[[@LINE+4]]:3: error: struct `{.x: type}` has no field named `y` [StructInitUnexpectedFieldInLiteral] + // CHECK:STDERR: {.y = type} as C.(Z.Z1); + // CHECK:STDERR: ^~~~~~~~~~~ + // CHECK:STDERR: + {.y = type} as C.(Z.Z1); +} + +// --- final_prioritization_first_declarared.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +final match_first { + // The match_first block is in the same order that the impls are introduced. + // This makes us prioritize the first introduced impl. + impl forall [T: Y] T as Z where .Z1 = {.y: type}; + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +fn F[U: type where .Self impls X and .Self impls Y]() { + // Since U impls X and Y, the first one in the match_first block + // should be selected, which is the .y impl. + {.y = type} as U.(Z.Z1); +} + +// --- final_prioritization_second_declarared.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +final match_first { + // The match_first block is in the opposite order that the impls are + // introduced. This makes us prioritize the second introduced impl. + impl forall [T: X] T as Z where .Z1 = {.x: type}; + impl forall [T: Y] T as Z where .Z1 = {.y: type}; +} + +fn F[U: type where .Self impls X and .Self impls Y]() { + // Since U impls X and Y, the first one in the match_first block + // should be selected, which is the .x impl. + {.x = type} as U.(Z.Z1); +} + +// --- fail_match_first_does_not_imply_final.carbon +library "[[@TEST_NAME]]"; + +interface Z { let Z1: type; } +interface Y {} +interface X {} + +impl forall [T: Y] T as Z where .Z1 = {.y: type} {} +impl forall [T: X] T as Z where .Z1 = {.x: type} {} + +match_first { + // The match_first block is in the same order that the impls are introduced. + // This makes us prioritize the first introduced impl. + impl forall [T: Y] T as Z where .Z1 = {.y: type}; + impl forall [T: X] T as Z where .Z1 = {.x: type}; +} + +fn F[U: type where .Self impls X and .Self impls Y]() { + // The impls are not final, so we can't use the concrete `.Z1` value provided + // by the impls here. This conversion should fail. + // CHECK:STDERR: fail_match_first_does_not_imply_final.carbon:[[@LINE+4]]:3: error: `Core.As` implicitly referenced here, but package `Core` not found [CoreNotFound] + // CHECK:STDERR: {.y = type} as U.(Z.Z1); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + {.y = type} as U.(Z.Z1); +} + +// --- non_final_more_specific_than_match_first.carbon +library "[[@TEST_NAME]]"; + +interface Z(T: type) { let Z1: type; } +interface Y {} +interface X {} +interface W {} + +class C(T: type); +impl forall [T: type] C(T) as W {} +impl forall [T: type] C(T) as Y {} + +// These impls have different type structures, but they overlap. For non-final +// impls we want to choose the most specific one (which will be the middle one), +// even if there is a match_first block for the others (but not for the middle +// one). +impl forall [T: W] () as Z(T) where .Z1 = {.w: type} {} +impl forall [T: X] () as Z(C(T)) where .Z1 = {.x: type} {} +impl forall [T: Y] () as Z(T) where .Z1 = {.y: type} {} + +match_first { + impl forall [T: W] () as Z(T) where .Z1 = {.w: type}; + impl forall [T: Y] () as Z(T) where .Z1 = {.y: type}; +} + +fn F() { + class D; + impl D as X {} + {.x = type} as ().(Z(C(D)).Z1); +} + +// --- non_final_overlap_in_match_first.carbon +library "[[@TEST_NAME]]"; + +interface Z(T: type) { let Z1: type; } +interface Y {} +interface X {} +interface W {} + +class C(T: type); +impl forall [T: type] C(T) as W {} +impl forall [T: type] C(T) as Y {} + +// These impls have different type structures, but they overlap. For non-final +// impls we would choose the most specific one (which will be the middle one), +// but then we find it is in a match_first block. So we use the first matching +// impl in that block instead. +impl forall [T: W] () as Z(T) where .Z1 = {.w: type} {} +impl forall [T: X] () as Z(C(T)) where .Z1 = {.x: type} {} +impl forall [T: Y] () as Z(T) where .Z1 = {.y: type} {} + +match_first { + impl forall [T: W] () as Z(T) where .Z1 = {.w: type}; + impl forall [T: X] () as Z(C(T)) where .Z1 = {.x: type}; + impl forall [T: Y] () as Z(T) where .Z1 = {.y: type}; +} + +fn F() { + class D; + impl D as X {} + {.w = type} as ().(Z(C(D)).Z1); +} + +// --- final_overlap_in_match_first.carbon +library "[[@TEST_NAME]]"; + +interface Z(T: type) { let Z1: type; } +interface Y {} +interface X {} +interface W {} + +class C(T: type); +impl forall [T: type] C(T) as W {} +impl forall [T: type] C(T) as Y {} + +// These impls have different type structures, but they overlap. Overlapping +// final impls are always in a match_first block together, so we will use the +// first impl from that block, not the most specific one. +impl forall [T: W] () as Z(T) where .Z1 = {.w: type} {} +impl forall [T: X] () as Z(C(T)) where .Z1 = {.x: type} {} +impl forall [T: Y] () as Z(T) where .Z1 = {.y: type} {} + +final match_first { + impl forall [T: Y] () as Z(T) where .Z1 = {.y: type}; + impl forall [T: X] () as Z(C(T)) where .Z1 = {.x: type}; + impl forall [T: W] () as Z(T) where .Z1 = {.w: type}; +} + +fn F[U: X]() { + {.y = type} as ().(Z(C(U)).Z1); +}