Skip to content

Commit cb5e2e1

Browse files
authored
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.
1 parent 10fab24 commit cb5e2e1

File tree

15 files changed

+1285
-100
lines changed

15 files changed

+1285
-100
lines changed

toolchain/check/convert.cpp

Lines changed: 103 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "toolchain/sem_ir/generic.h"
3232
#include "toolchain/sem_ir/ids.h"
3333
#include "toolchain/sem_ir/inst.h"
34+
#include "toolchain/sem_ir/type.h"
3435
#include "toolchain/sem_ir/typed_insts.h"
3536

3637
// 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,
767768
return InitReprIsCopyOfValueRepr(sem_ir, type_id);
768769
}
769770

771+
// Determine whether the given set of qualifiers can be added by a conversion
772+
// of an expression of the given category.
773+
static auto CanAddQualifiers(SemIR::TypeQualifiers quals,
774+
SemIR::ExprCategory cat) -> bool {
775+
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
776+
!SemIR::IsRefCategory(cat)) {
777+
// `MaybeUnformed(T)` may have a different value representation or
778+
// initializing representation from `T`, so only allow it to be added for a
779+
// reference expression.
780+
// TODO: We should allow converting an initializing expression of type `T`
781+
// to `MaybeUnformed(T)`. `PerformBuiltinConversion` will need to generate
782+
// an `InPlaceInit` instruction when needed.
783+
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
784+
return false;
785+
}
786+
787+
// `const` and `partial` can always be added.
788+
return true;
789+
}
790+
791+
// Determine whether the given set of qualifiers can be removed by a conversion
792+
// of an expression of the given category.
793+
static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
794+
SemIR::ExprCategory cat, bool allow_unsafe)
795+
-> bool {
796+
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Const) && !allow_unsafe &&
797+
SemIR::IsRefCategory(cat)) {
798+
// Removing `const` is an unsafe conversion for a reference expression.
799+
return false;
800+
}
801+
802+
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::Partial) &&
803+
(!allow_unsafe || cat == SemIR::ExprCategory::Initializing)) {
804+
// TODO: Allow removing `partial` for initializing expressions as a safe
805+
// conversion. `PerformBuiltinConversion` will need to initialize the vptr
806+
// as part of the conversion.
807+
return false;
808+
}
809+
810+
if (HasTypeQualifier(quals, SemIR::TypeQualifiers::MaybeUnformed) &&
811+
(!allow_unsafe || !SemIR::IsRefCategory(cat))) {
812+
// As an unsafe conversion, `MaybeUnformed` can be removed from a reference
813+
// expression.
814+
// TODO: We should allow this for any kind of expression, and convert the
815+
// result as needed if the representation of `T` differs from that of
816+
// `MaybeUnformed(T)`.
817+
return false;
818+
}
819+
820+
return true;
821+
}
822+
770823
static auto DiagnoseConversionFailureToConstraintValue(
771824
Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
772825
SemIR::TypeId target_type_id) -> void {
@@ -914,24 +967,32 @@ static auto PerformBuiltinConversion(
914967
}
915968
}
916969

917-
// T explicitly converts to U if T is compatible with U.
970+
// T explicitly converts to U if T is compatible with U, and we're allowed to
971+
// remove / add any qualifiers that differ.
918972
if (target.is_explicit_as() && target.type_id != value_type_id) {
919-
auto target_foundation_id =
920-
context.types().GetTransitiveAdaptedType(target.type_id);
921-
auto value_foundation_id =
922-
context.types().GetTransitiveAdaptedType(value_type_id);
973+
auto [target_foundation_id, target_quals] =
974+
context.types().GetTransitiveUnqualifiedAdaptedType(target.type_id);
975+
auto [value_foundation_id, value_quals] =
976+
context.types().GetTransitiveUnqualifiedAdaptedType(value_type_id);
923977
if (target_foundation_id == value_foundation_id) {
924-
// For a struct or tuple literal, perform a category conversion if
925-
// necessary.
926-
if (SemIR::GetExprCategory(context.sem_ir(), value_id) ==
927-
SemIR::ExprCategory::Mixed) {
928-
value_id = PerformBuiltinConversion(context, loc_id, value_id,
929-
{.kind = ConversionTarget::Value,
930-
.type_id = value_type_id,
931-
.diagnose = target.diagnose});
978+
auto category = SemIR::GetExprCategory(context.sem_ir(), value_id);
979+
if (CanAddQualifiers(target_quals & ~value_quals, category) &&
980+
CanRemoveQualifiers(
981+
value_quals & ~target_quals, category,
982+
target.kind == ConversionTarget::ExplicitUnsafeAs)) {
983+
// For a struct or tuple literal, perform a category conversion if
984+
// necessary.
985+
if (category == SemIR::ExprCategory::Mixed) {
986+
value_id = PerformBuiltinConversion(context, loc_id, value_id,
987+
{.kind = ConversionTarget::Value,
988+
.type_id = value_type_id,
989+
.diagnose = target.diagnose});
990+
}
991+
992+
return AddInst<SemIR::AsCompatible>(
993+
context, loc_id,
994+
{.type_id = target.type_id, .source_id = value_id});
932995
}
933-
return AddInst<SemIR::AsCompatible>(
934-
context, loc_id, {.type_id = target.type_id, .source_id = value_id});
935996
}
936997
}
937998

@@ -996,19 +1057,23 @@ static auto PerformBuiltinConversion(
9961057
}
9971058
}
9981059

999-
// A pointer T* converts to [const] U* if T is the same as U, or is a class
1000-
// derived from U.
1060+
// A pointer T* converts to [qualified] U* if T is the same as U, or is a
1061+
// class derived from U.
10011062
if (auto target_pointer_type = target_type_inst.TryAs<SemIR::PointerType>()) {
10021063
if (auto src_pointer_type =
10031064
sem_ir.types().TryGetAs<SemIR::PointerType>(value_type_id)) {
1065+
auto target_pointee_id = context.types().GetTypeIdForTypeInstId(
1066+
target_pointer_type->pointee_id);
1067+
auto src_pointee_id =
1068+
context.types().GetTypeIdForTypeInstId(src_pointer_type->pointee_id);
1069+
// Try to complete the pointee types so that we can walk through adapters
1070+
// to their adapted types.
1071+
TryToCompleteType(context, target_pointee_id, loc_id);
1072+
TryToCompleteType(context, src_pointee_id, loc_id);
10041073
auto [unqual_target_pointee_type_id, target_quals] =
1005-
sem_ir.types().GetUnqualifiedTypeAndQualifiers(
1006-
context.types().GetTypeIdForTypeInstId(
1007-
target_pointer_type->pointee_id));
1074+
sem_ir.types().GetTransitiveUnqualifiedAdaptedType(target_pointee_id);
10081075
auto [unqual_src_pointee_type_id, src_quals] =
1009-
sem_ir.types().GetUnqualifiedTypeAndQualifiers(
1010-
context.types().GetTypeIdForTypeInstId(
1011-
src_pointer_type->pointee_id));
1076+
sem_ir.types().GetTransitiveUnqualifiedAdaptedType(src_pointee_id);
10121077

10131078
// If the qualifiers are incompatible, we can't perform a conversion,
10141079
// except with `unsafe as`.
@@ -1366,25 +1431,30 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
13661431
if (!target.diagnose) {
13671432
return context.emitter().BuildSuppressed();
13681433
}
1434+
int target_kind_for_diag =
1435+
target.kind == ConversionTarget::ExplicitAs ? 1
1436+
: target.kind == ConversionTarget::ExplicitUnsafeAs ? 2
1437+
: 0;
13691438
if (target.type_id == SemIR::TypeType::TypeId ||
13701439
sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
13711440
CARBON_DIAGNOSTIC(
13721441
ConversionFailureNonTypeToFacet, Error,
1373-
"cannot{0:| implicitly} convert non-type value of type {1} "
1374-
"{2:to|into type implementing} {3}{0: with `as`|}",
1375-
Diagnostics::BoolAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
1442+
"cannot{0:=0: implicitly|:} convert non-type value of type {1} "
1443+
"{2:to|into type implementing} {3}"
1444+
"{0:=1: with `as`|=2: with `unsafe as`|:}",
1445+
Diagnostics::IntAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
13761446
SemIR::TypeId);
13771447
return context.emitter().Build(
1378-
loc_id, ConversionFailureNonTypeToFacet, target.is_explicit_as(),
1448+
loc_id, ConversionFailureNonTypeToFacet, target_kind_for_diag,
13791449
expr_id, target.type_id == SemIR::TypeType::TypeId, target.type_id);
13801450
} else {
1381-
CARBON_DIAGNOSTIC(ConversionFailure, Error,
1382-
"cannot{0:| implicitly} convert expression of type "
1383-
"{1} to {2}{0: with `as`|}",
1384-
Diagnostics::BoolAsSelect, TypeOfInstId,
1385-
SemIR::TypeId);
1451+
CARBON_DIAGNOSTIC(
1452+
ConversionFailure, Error,
1453+
"cannot{0:=0: implicitly|:} convert expression of type "
1454+
"{1} to {2}{0:=1: with `as`|=2: with `unsafe as`|:}",
1455+
Diagnostics::IntAsSelect, TypeOfInstId, SemIR::TypeId);
13861456
return context.emitter().Build(loc_id, ConversionFailure,
1387-
target.is_explicit_as(), expr_id,
1457+
target_kind_for_diag, expr_id,
13881458
target.type_id);
13891459
}
13901460
});

toolchain/check/import_ref.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "toolchain/sem_ir/ids.h"
2727
#include "toolchain/sem_ir/import_ir.h"
2828
#include "toolchain/sem_ir/inst.h"
29+
#include "toolchain/sem_ir/inst_categories.h"
2930
#include "toolchain/sem_ir/inst_kind.h"
3031
#include "toolchain/sem_ir/type_info.h"
3132
#include "toolchain/sem_ir/typed_insts.h"
@@ -1823,14 +1824,16 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
18231824
.object_repr_type_inst_id = object_repr_type_inst_id});
18241825
}
18251826

1826-
static auto TryResolveTypedInst(ImportRefResolver& resolver,
1827-
SemIR::ConstType inst) -> ResolveResult {
1827+
template <typename InstT>
1828+
requires SemIR::Internal::HasInstCategory<SemIR::AnyQualifiedType, InstT>
1829+
static auto TryResolveTypedInst(ImportRefResolver& resolver, InstT inst)
1830+
-> ResolveResult {
18281831
CARBON_CHECK(inst.type_id == SemIR::TypeType::TypeId);
18291832
auto inner_id = GetLocalTypeInstId(resolver, inst.inner_id);
18301833
if (resolver.HasNewWork()) {
18311834
return ResolveResult::Retry();
18321835
}
1833-
return ResolveAsDeduplicated<SemIR::ConstType>(
1836+
return ResolveAsDeduplicated<InstT>(
18341837
resolver, {.type_id = SemIR::TypeType::TypeId, .inner_id = inner_id});
18351838
}
18361839

@@ -3183,12 +3186,18 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver,
31833186
case CARBON_KIND(SemIR::IntType inst): {
31843187
return TryResolveTypedInst(resolver, inst);
31853188
}
3189+
case CARBON_KIND(SemIR::MaybeUnformedType inst): {
3190+
return TryResolveTypedInst(resolver, inst);
3191+
}
31863192
case CARBON_KIND(SemIR::Namespace inst): {
31873193
return TryResolveTypedInst(resolver, inst, inst_id);
31883194
}
31893195
case CARBON_KIND(SemIR::OutParamPattern inst): {
31903196
return TryResolveTypedInst(resolver, inst, inst_id);
31913197
}
3198+
case CARBON_KIND(SemIR::PartialType inst): {
3199+
return TryResolveTypedInst(resolver, inst);
3200+
}
31923201
case CARBON_KIND(SemIR::PatternType inst): {
31933202
return TryResolveTypedInst(resolver, inst);
31943203
}

0 commit comments

Comments
 (0)