Improve support for qualification conversions. (#5999)

* Treat `MaybeUnformed` and `partial` as qualifiers, like `const`.
* Allow pointer conversions to add qualifiers.
* Allow unsafe pointer conversions to remove qualifiers.
* Allow conversions on non-reference expressions to drop `const`.
* Allow unsafe conversions on any expression to drop `const`.
* Allow unsafe conversions on non-initializing expressions to drop
  `partial`. For initializing expressions, we should initialize the
  vptr when dropping `partial`; this is not yet supported so we reject.
* Allow conversions on reference expressions to add `MaybeUnformed`.
* Allow unsafe conversions on reference expressions to drop
  `MaybeUnformed`. For non-reference expressions, additional work is
  required, because the value / initializing representation may not
  match between `T` and `MaybeUnformed(T)`, so those are rejected for
  now.
This commit is contained in:
Richard Smith
2025-09-03 21:00:12 +00:00
committed by GitHub
parent 10fab24451
commit cb5e2e1597
15 changed files with 1285 additions and 100 deletions
+103 -33
View File
@@ -31,6 +31,7 @@
#include "toolchain/sem_ir/generic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
#include "toolchain/sem_ir/type.h"
#include "toolchain/sem_ir/typed_insts.h"
// TODO: This contains a lot of recursion. Consider removing it in order to
@@ -767,6 +768,58 @@ static auto CanUseValueOfInitializer(const SemIR::File& sem_ir,
return InitReprIsCopyOfValueRepr(sem_ir, type_id);
}
// Determine whether the given set of qualifiers can be added by a conversion
// of an expression of the given category.
static auto CanAddQualifiers(SemIR::TypeQualifiers quals,
SemIR::ExprCategory cat) -> bool {
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
!SemIR::IsRefCategory(cat)) {
// `MaybeUnformed(T)` may have a different value representation or
// initializing representation from `T`, so only allow it to be added for a
// reference expression.
// TODO: We should allow converting an initializing expression of type `T`
// to `MaybeUnformed(T)`. `PerformBuiltinConversion` will need to generate
// an `InPlaceInit` instruction when needed.
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
return false;
}
// `const` and `partial` can always be added.
return true;
}
// Determine whether the given set of qualifiers can be removed by a conversion
// of an expression of the given category.
static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
SemIR::ExprCategory cat, bool allow_unsafe)
-> bool {
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Const) && !allow_unsafe &&
SemIR::IsRefCategory(cat)) {
// Removing `const` is an unsafe conversion for a reference expression.
return false;
}
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Partial) &&
(!allow_unsafe || cat == SemIR::ExprCategory::Initializing)) {
// TODO: Allow removing `partial` for initializing expressions as a safe
// conversion. `PerformBuiltinConversion` will need to initialize the vptr
// as part of the conversion.
return false;
}
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
(!allow_unsafe || !SemIR::IsRefCategory(cat))) {
// As an unsafe conversion, `MaybeUnformed` can be removed from a reference
// expression.
// TODO: We should allow this for any kind of expression, and convert the
// result as needed if the representation of `T` differs from that of
// `MaybeUnformed(T)`.
return false;
}
return true;
}
static auto DiagnoseConversionFailureToConstraintValue(
Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
SemIR::TypeId target_type_id) -> void {
@@ -914,24 +967,32 @@ static auto PerformBuiltinConversion(
}
}
// T explicitly converts to U if T is compatible with U.
// T explicitly converts to U if T is compatible with U, and we're allowed to
// remove / add any qualifiers that differ.
if (target.is_explicit_as() && target.type_id != value_type_id) {
auto target_foundation_id =
context.types().GetTransitiveAdaptedType(target.type_id);
auto value_foundation_id =
context.types().GetTransitiveAdaptedType(value_type_id);
auto [target_foundation_id, target_quals] =
context.types().GetTransitiveUnqualifiedAdaptedType(target.type_id);
auto [value_foundation_id, value_quals] =
context.types().GetTransitiveUnqualifiedAdaptedType(value_type_id);
if (target_foundation_id == value_foundation_id) {
// For a struct or tuple literal, perform a category conversion if
// necessary.
if (SemIR::GetExprCategory(context.sem_ir(), value_id) ==
SemIR::ExprCategory::Mixed) {
value_id = PerformBuiltinConversion(context, loc_id, value_id,
{.kind = ConversionTarget::Value,
.type_id = value_type_id,
.diagnose = target.diagnose});
auto category = SemIR::GetExprCategory(context.sem_ir(), value_id);
if (CanAddQualifiers(target_quals & ~value_quals, category) &&
CanRemoveQualifiers(
value_quals & ~target_quals, category,
target.kind == ConversionTarget::ExplicitUnsafeAs)) {
// For a struct or tuple literal, perform a category conversion if
// necessary.
if (category == SemIR::ExprCategory::Mixed) {
value_id = PerformBuiltinConversion(context, loc_id, value_id,
{.kind = ConversionTarget::Value,
.type_id = value_type_id,
.diagnose = target.diagnose});
}
return AddInst<SemIR::AsCompatible>(
context, loc_id,
{.type_id = target.type_id, .source_id = value_id});
}
return AddInst<SemIR::AsCompatible>(
context, loc_id, {.type_id = target.type_id, .source_id = value_id});
}
}
@@ -996,19 +1057,23 @@ static auto PerformBuiltinConversion(
}
}
// A pointer T* converts to [const] U* if T is the same as U, or is a class
// derived from U.
// A pointer T* converts to [qualified] U* if T is the same as U, or is a
// class derived from U.
if (auto target_pointer_type = target_type_inst.TryAs<SemIR::PointerType>()) {
if (auto src_pointer_type =
sem_ir.types().TryGetAs<SemIR::PointerType>(value_type_id)) {
auto target_pointee_id = context.types().GetTypeIdForTypeInstId(
target_pointer_type->pointee_id);
auto src_pointee_id =
context.types().GetTypeIdForTypeInstId(src_pointer_type->pointee_id);
// Try to complete the pointee types so that we can walk through adapters
// to their adapted types.
TryToCompleteType(context, target_pointee_id, loc_id);
TryToCompleteType(context, src_pointee_id, loc_id);
auto [unqual_target_pointee_type_id, target_quals] =
sem_ir.types().GetUnqualifiedTypeAndQualifiers(
context.types().GetTypeIdForTypeInstId(
target_pointer_type->pointee_id));
sem_ir.types().GetTransitiveUnqualifiedAdaptedType(target_pointee_id);
auto [unqual_src_pointee_type_id, src_quals] =
sem_ir.types().GetUnqualifiedTypeAndQualifiers(
context.types().GetTypeIdForTypeInstId(
src_pointer_type->pointee_id));
sem_ir.types().GetTransitiveUnqualifiedAdaptedType(src_pointee_id);
// If the qualifiers are incompatible, we can't perform a conversion,
// except with `unsafe as`.
@@ -1366,25 +1431,30 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
if (!target.diagnose) {
return context.emitter().BuildSuppressed();
}
int target_kind_for_diag =
target.kind == ConversionTarget::ExplicitAs ? 1
: target.kind == ConversionTarget::ExplicitUnsafeAs ? 2
: 0;
if (target.type_id == SemIR::TypeType::TypeId ||
sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
CARBON_DIAGNOSTIC(
ConversionFailureNonTypeToFacet, Error,
"cannot{0:| implicitly} convert non-type value of type {1} "
"{2:to|into type implementing} {3}{0: with `as`|}",
Diagnostics::BoolAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
"cannot{0:=0: implicitly|:} convert non-type value of type {1} "
"{2:to|into type implementing} {3}"
"{0:=1: with `as`|=2: with `unsafe as`|:}",
Diagnostics::IntAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
SemIR::TypeId);
return context.emitter().Build(
loc_id, ConversionFailureNonTypeToFacet, target.is_explicit_as(),
loc_id, ConversionFailureNonTypeToFacet, target_kind_for_diag,
expr_id, target.type_id == SemIR::TypeType::TypeId, target.type_id);
} else {
CARBON_DIAGNOSTIC(ConversionFailure, Error,
"cannot{0:| implicitly} convert expression of type "
"{1} to {2}{0: with `as`|}",
Diagnostics::BoolAsSelect, TypeOfInstId,
SemIR::TypeId);
CARBON_DIAGNOSTIC(
ConversionFailure, Error,
"cannot{0:=0: implicitly|:} convert expression of type "
"{1} to {2}{0:=1: with `as`|=2: with `unsafe as`|:}",
Diagnostics::IntAsSelect, TypeOfInstId, SemIR::TypeId);
return context.emitter().Build(loc_id, ConversionFailure,
target.is_explicit_as(), expr_id,
target_kind_for_diag, expr_id,
target.type_id);
}
});