Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/prelude/types/int.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl forall [N:! IntLiteral()] Int(N) as Copy {

// Conversions.

impl forall [To:! IntLiteral()] IntLiteral() as ImplicitAs(Int(To)) {
final impl forall [To:! IntLiteral()] IntLiteral() as ImplicitAs(Int(To)) {
fn Convert[self: Self]() -> Int(To) = "int.convert_checked";
}

Expand All @@ -32,7 +32,7 @@ final impl forall [From:! IntLiteral()] Int(From) as ImplicitAs(IntLiteral()) {
}

// TODO: Remove these once ImplicitAs extends As.
impl forall [To:! IntLiteral()] IntLiteral() as As(Int(To)) {
final impl forall [To:! IntLiteral()] IntLiteral() as As(Int(To)) {
fn Convert[self: Self]() -> Int(To) = "int.convert_checked";
}

Expand Down
4 changes: 2 additions & 2 deletions core/prelude/types/uint.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl forall [N:! IntLiteral()] UInt(N) as Copy {

// Conversions.

impl forall [To:! IntLiteral()] IntLiteral() as ImplicitAs(UInt(To)) {
final impl forall [To:! IntLiteral()] IntLiteral() as ImplicitAs(UInt(To)) {
fn Convert[self: Self]() -> UInt(To) = "int.convert_checked";
}

Expand All @@ -33,7 +33,7 @@ final impl forall [From:! IntLiteral()] UInt(From) as ImplicitAs(IntLiteral()) {
}

// TODO: Remove these once ImplicitAs extends As.
impl forall [To:! IntLiteral()] IntLiteral() as As(UInt(To)) {
final impl forall [To:! IntLiteral()] IntLiteral() as As(UInt(To)) {
fn Convert[self: Self]() -> UInt(To) = "int.convert_checked";
}

Expand Down
1 change: 1 addition & 0 deletions toolchain/check/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "toolchain/check/type_completion.h"
#include "toolchain/check/type_structure.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused?

#include "toolchain/sem_ir/generic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/impl.h"
Expand Down
92 changes: 90 additions & 2 deletions toolchain/check/impl_validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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);
Expand Down Expand Up @@ -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;
}
Expand Down
168 changes: 168 additions & 0 deletions toolchain/check/testdata/impl/final_not_specializable.carbon
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; }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)) {}
18 changes: 9 additions & 9 deletions toolchain/check/testdata/impl/lookup/impl_overlap.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,11 @@ class C;

// The root type must be defined in the same library, not just declared.
//
// CHECK:STDERR: fail_final_root_self_type_not_defined.carbon:[[@LINE+4]]:1: error: `final impl` found in file that does not contain the root self type nor the interface definition [FinalImplInvalidFile]
// CHECK:STDERR: final impl C as Z(()) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_final_root_self_type_not_defined.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] C as Z(T) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
final impl C as Z(()) {}
final impl forall [T:! type] C as Z(T) {}

// --- fail_final_different_file_from_self_and_interface.carbon
library "[[@TEST_NAME]]";
Expand All @@ -263,11 +263,11 @@ class C {}
// Can't make a final impl that is not in the same file as the self type nor
// the interface.
//
// CHECK:STDERR: fail_final_different_file_from_self_and_interface.carbon:[[@LINE+4]]:1: error: `final impl` found in file that does not contain the root self type nor the interface definition [FinalImplInvalidFile]
// CHECK:STDERR: final impl D(C) as Z(()) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_final_different_file_from_self_and_interface.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] D(C) as Z(T) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
final impl D(C) as Z(()) {}
final impl forall [T:! type] D(C) as Z(T) {}

// --- fail_final_different_file_from_self_and_interface_with_generic_self.carbon
library "[[@TEST_NAME]]";
Expand All @@ -279,7 +279,7 @@ class C {}
// Can't make a final impl that is not in the same file as the self type nor
// the interface.
//
// CHECK:STDERR: fail_final_different_file_from_self_and_interface_with_generic_self.carbon:[[@LINE+4]]:1: error: `final impl` found in file that does not contain the root self type nor the interface definition [FinalImplInvalidFile]
// CHECK:STDERR: fail_final_different_file_from_self_and_interface_with_generic_self.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] T as Z(C) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Expand Down
Loading
Loading