mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 07:24:42 +01:00
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:
+103
-33
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user