-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Allow impls with sufficiently primitive generic parameters to be declared final. #6511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
3e4d26e
bf71a11
7ed91e3
5312c22
5527abf
39c6371
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,6 +77,91 @@ static auto IsSameLibrary(Context& context, SemIR::InstId owning_inst_id) | |
| return false; | ||
| } | ||
|
|
||
| // Determines whether the given generic parameter type can have a value that | ||
| // introduces additional associated libraries during impl lookup. | ||
| static auto TypeCanIntroduceAssociatedLibraries(Context& context, | ||
| SemIR::TypeId initial_type_id) | ||
| -> bool { | ||
| llvm::SmallVector<SemIR::TypeId> worklist = {initial_type_id}; | ||
| auto push_type_inst_id = [&](SemIR::TypeInstId type_inst_id) { | ||
| worklist.push_back(context.types().GetTypeIdForTypeInstId(type_inst_id)); | ||
| }; | ||
|
|
||
| while (!worklist.empty()) { | ||
| auto type_id = worklist.pop_back_val(); | ||
|
|
||
| // Remove type qualifiers and look through adapters. | ||
| type_id = context.types().GetObjectRepr(type_id); | ||
| if (!type_id.has_value()) { | ||
| // TODO: How should we handle the case where the parameter type is | ||
| // incomplete? Can we defer determining whether an impl is effectively | ||
| // final until we see its definition? It's not clear that a parameter of | ||
| // incomplete type can be used in an impl declaration, so this may not | ||
| // matter. For now we treat such impls as not effectively final. | ||
| return false; | ||
| } | ||
|
|
||
| CARBON_KIND_SWITCH(context.types().GetAsInst(type_id)) { | ||
| case CARBON_KIND(SemIR::ArrayType inst): { | ||
| push_type_inst_id(inst.element_type_inst_id); | ||
| break; | ||
| } | ||
| case CARBON_KIND(SemIR::StructType inst): { | ||
| for (auto field : context.struct_type_fields().Get(inst.fields_id)) { | ||
| push_type_inst_id(field.type_inst_id); | ||
| } | ||
| break; | ||
| } | ||
| case CARBON_KIND(SemIR::TupleType inst): { | ||
| for (auto inst_id : context.inst_blocks().Get(inst.type_elements_id)) { | ||
| push_type_inst_id(context.types().GetAsTypeInstId(inst_id)); | ||
| } | ||
| break; | ||
| } | ||
| case SemIR::BoolType::Kind: | ||
| case SemIR::CharLiteralType::Kind: | ||
| case SemIR::FloatLiteralType::Kind: | ||
| case SemIR::FloatType::Kind: | ||
| case SemIR::IntLiteralType::Kind: | ||
| case SemIR::IntType::Kind: { | ||
| // Values of these types cannot introduce any associated library | ||
| // dependence. | ||
| return false; | ||
| } | ||
| default: { | ||
| // Conservatively assume any other type can introduce associated | ||
| // libraries. | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // Returns whether the given `impl` is specializable in libraries other than the | ||
| // one in which it's defined. If not, we allow it to be declared as `final`. | ||
| static auto IsImplExternallySpecializable(Context& context, | ||
| const SemIR::Impl& impl) -> bool { | ||
| // If the impl is not generic, it can't be specialized. | ||
| if (!impl.generic_id.has_value()) { | ||
| return false; | ||
| } | ||
|
|
||
| // If the types of all the generic bindings for the impl do not allow values | ||
| // with associated libraries, then no further specialization is possible in | ||
| // any other library, so the impl is effectively final. | ||
|
Comment on lines
+150
to
+152
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this comment might need to be updated. It's not wrong but it's talking about the old context of this PR, but here we're checking for an impl being externally specializable, a somewhat inverse of effectively final |
||
| for (auto binding_id : context.inst_blocks().Get( | ||
| context.generics().Get(impl.generic_id).bindings_id)) { | ||
| if (TypeCanIntroduceAssociatedLibraries( | ||
| context, context.insts().Get(binding_id).type_id())) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| // All bindings are non-type bindings, and so cannot be specialized. | ||
| return false; | ||
| } | ||
|
|
||
| static auto GetImplInfo(Context& context, SemIR::ImplId impl_id) -> ImplInfo { | ||
| const auto& impl = context.impls().Get(impl_id); | ||
| auto ir_id = GetIRId(context, impl.first_owning_decl_id); | ||
|
|
@@ -132,10 +217,13 @@ static auto DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface( | |
|
|
||
| bool interface_same_file = !interface_ir_id.has_value(); | ||
|
|
||
| if (!self_type_same_file && !interface_same_file) { | ||
| if (!self_type_same_file && !interface_same_file && | ||
| IsImplExternallySpecializable(context, | ||
| context.impls().Get(impl.impl_id))) { | ||
| CARBON_DIAGNOSTIC(FinalImplInvalidFile, Error, | ||
| "`final impl` found in file that does not contain " | ||
| "the root self type nor the interface definition"); | ||
| "the root self type nor the interface definition, " | ||
| "and might be specialized in other files"); | ||
| context.emitter().Emit(impl.latest_decl_id, FinalImplInvalidFile); | ||
| return true; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| // 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/primitives.carbon | ||
| // | ||
| // AUTOUPDATE | ||
| // TIP: To test this file alone, run: | ||
| // TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/final_not_specializable.carbon | ||
| // TIP: To dump output, run: | ||
| // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/final_not_specializable.carbon | ||
|
|
||
| // --- interface.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| interface I(T:! type) {} | ||
|
|
||
| // --- not_specializable.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| import library "interface"; | ||
|
|
||
| class A[T:! type](V:! T) {} | ||
|
|
||
| // An impl is allowed to be declared as final if all of its generic bindings are | ||
| // of the following types: | ||
|
|
||
| // - `bool` | ||
|
|
||
| final impl forall [B:! bool] () as I(A(B)) {} | ||
|
|
||
| // - character literal type | ||
|
|
||
| final impl forall [N:! Core.CharLiteral()] () as I(A(N)) {} | ||
|
|
||
| // - integer type | ||
|
|
||
| final impl forall [N:! i32] () as I(A(N)) {} | ||
|
|
||
| // - integer literal type | ||
|
|
||
| final impl forall [N:! Core.IntLiteral()] () as I(A(N)) {} | ||
|
|
||
| // - floating-point type | ||
|
|
||
| final impl forall [N:! f64] () as I(A(N)) {} | ||
|
|
||
| // - floating-point literal type | ||
|
|
||
| final impl forall [N:! Core.FloatLiteral()] () as I(A(N)) {} | ||
|
|
||
| // - array of such types | ||
|
|
||
| final impl forall [Arr:! array(i32, 4)] () as I(A(Arr)) {} | ||
|
|
||
| // - struct containing such types | ||
|
|
||
| final impl forall [S:! {.a: i32, .b: f64}] () as I(A(S)) {} | ||
|
|
||
| // - tuple containing such types | ||
|
|
||
| final impl forall [T:! (i32, f64)] () as I(A(T)) {} | ||
|
|
||
| // - qualified form of such a type | ||
|
|
||
| final impl forall [N:! const i32] () as I(A(N)) {} | ||
|
|
||
| // - adapter for such a type | ||
|
|
||
| class Adapt { adapt i32; } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we test an Adapt of an array of i32? |
||
| final impl forall [N:! Adapt] () as I(A(N)) {} | ||
|
|
||
| // - (recursively) | ||
|
|
||
| final impl forall [T:! (i32, {.x: array(f64, 4)})] () as I(A(T)) {} | ||
|
|
||
| // --- fail_specializable_type.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| import library "interface"; | ||
|
|
||
| class A[T:! type](V:! T) {} | ||
|
|
||
| // An impl with a binding of any other type can't be declared `final` except in | ||
| // the library that owns its root type or interface, as it could be further | ||
| // specialized. | ||
|
|
||
| // CHECK:STDERR: fail_specializable_type.carbon:[[@LINE+4]]:1: error: `final impl` found in file that does not contain the root self type nor the interface definition, and might be specialized in other files [FinalImplInvalidFile] | ||
| // CHECK:STDERR: final impl forall [T:! type] () as I(A(T)) {} | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: | ||
| final impl forall [T:! type] () as I(A(T)) {} | ||
|
|
||
| // --- fail_specializable_facet.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| import library "interface"; | ||
|
|
||
| class A[T:! type](V:! T) {} | ||
|
|
||
| interface B {} | ||
|
|
||
| // CHECK:STDERR: fail_specializable_facet.carbon:[[@LINE+4]]:1: error: `final impl` found in file that does not contain the root self type nor the interface definition, and might be specialized in other files [FinalImplInvalidFile] | ||
| // CHECK:STDERR: final impl forall [T:! B] () as I(A(T)) {} | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: | ||
| final impl forall [T:! B] () as I(A(T)) {} | ||
|
|
||
| // --- fail_specializable_nested.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| import library "interface"; | ||
|
|
||
| class A[T:! type](V:! T) {} | ||
|
|
||
| // CHECK:STDERR: fail_specializable_nested.carbon:[[@LINE+4]]:1: error: `final impl` found in file that does not contain the root self type nor the interface definition, and might be specialized in other files [FinalImplInvalidFile] | ||
| // CHECK:STDERR: final impl forall [T:! (i32, {.x: type})] () as I(A(T)) {} | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: | ||
| final impl forall [T:! (i32, {.x: type})] () as I(A(T)) {} | ||
|
|
||
| // --- fail_not_effectively_final_incomplete.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| import library "interface"; | ||
|
|
||
| class A[T:! type](V:! T) {} | ||
|
|
||
| // We can't tell whether a parameter of an incomplete type would make the impl | ||
| // effectively final, but it doesn't matter because the impl is invalid anyway. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "would make the impl unable to be specialized externally"? Does this test still make sense? |
||
| class Incomplete; | ||
|
|
||
| // CHECK:STDERR: fail_not_effectively_final_incomplete.carbon:[[@LINE+10]]:44: error: forming value of incomplete type `Incomplete` [IncompleteTypeInValueConversion] | ||
| // CHECK:STDERR: final impl forall [T:! Incomplete] () as I(A(T)) {} | ||
| // CHECK:STDERR: ^~~~ | ||
| // CHECK:STDERR: fail_not_effectively_final_incomplete.carbon:[[@LINE-5]]:1: note: class was forward declared here [ClassForwardDeclaredHere] | ||
| // CHECK:STDERR: class Incomplete; | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: fail_not_effectively_final_incomplete.carbon:[[@LINE-12]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] | ||
| // CHECK:STDERR: class A[T:! type](V:! T) {} | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: | ||
| final impl forall [T:! Incomplete] () as I(A(T)) {} | ||
|
|
||
| // --- fail_final_specialized.carbon | ||
|
|
||
| library "[[@TEST_NAME]]"; | ||
|
|
||
| import library "interface"; | ||
|
|
||
| class A[T:! type](V:! T) {} | ||
|
|
||
| final impl forall [B:! bool] () as I(A(B)) {} | ||
|
|
||
| // CHECK:STDERR: fail_final_specialized.carbon:[[@LINE+7]]:1: error: `impl` will never be used [ImplFinalOverlapsNonFinal] | ||
| // CHECK:STDERR: impl () as I(A(true)) {} | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: fail_final_specialized.carbon:[[@LINE-5]]:1: note: `final impl` declared here would always be used instead [ImplFinalOverlapsNonFinalNote] | ||
| // CHECK:STDERR: final impl forall [B:! bool] () as I(A(B)) {} | ||
| // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| // CHECK:STDERR: | ||
| impl () as I(A(true)) {} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused?