|
31 | 31 | #include "toolchain/sem_ir/generic.h"
|
32 | 32 | #include "toolchain/sem_ir/ids.h"
|
33 | 33 | #include "toolchain/sem_ir/inst.h"
|
| 34 | +#include "toolchain/sem_ir/type.h" |
34 | 35 | #include "toolchain/sem_ir/typed_insts.h"
|
35 | 36 |
|
36 | 37 | // 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,
|
767 | 768 | return InitReprIsCopyOfValueRepr(sem_ir, type_id);
|
768 | 769 | }
|
769 | 770 |
|
| 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 | + |
770 | 823 | static auto DiagnoseConversionFailureToConstraintValue(
|
771 | 824 | Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
|
772 | 825 | SemIR::TypeId target_type_id) -> void {
|
@@ -914,24 +967,32 @@ static auto PerformBuiltinConversion(
|
914 | 967 | }
|
915 | 968 | }
|
916 | 969 |
|
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. |
918 | 972 | 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); |
923 | 977 | 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}); |
932 | 995 | }
|
933 |
| - return AddInst<SemIR::AsCompatible>( |
934 |
| - context, loc_id, {.type_id = target.type_id, .source_id = value_id}); |
935 | 996 | }
|
936 | 997 | }
|
937 | 998 |
|
@@ -996,19 +1057,23 @@ static auto PerformBuiltinConversion(
|
996 | 1057 | }
|
997 | 1058 | }
|
998 | 1059 |
|
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. |
1001 | 1062 | if (auto target_pointer_type = target_type_inst.TryAs<SemIR::PointerType>()) {
|
1002 | 1063 | if (auto src_pointer_type =
|
1003 | 1064 | 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); |
1004 | 1073 | 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); |
1008 | 1075 | 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); |
1012 | 1077 |
|
1013 | 1078 | // If the qualifiers are incompatible, we can't perform a conversion,
|
1014 | 1079 | // except with `unsafe as`.
|
@@ -1366,25 +1431,30 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
|
1366 | 1431 | if (!target.diagnose) {
|
1367 | 1432 | return context.emitter().BuildSuppressed();
|
1368 | 1433 | }
|
| 1434 | + int target_kind_for_diag = |
| 1435 | + target.kind == ConversionTarget::ExplicitAs ? 1 |
| 1436 | + : target.kind == ConversionTarget::ExplicitUnsafeAs ? 2 |
| 1437 | + : 0; |
1369 | 1438 | if (target.type_id == SemIR::TypeType::TypeId ||
|
1370 | 1439 | sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
|
1371 | 1440 | CARBON_DIAGNOSTIC(
|
1372 | 1441 | 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, |
1376 | 1446 | SemIR::TypeId);
|
1377 | 1447 | return context.emitter().Build(
|
1378 |
| - loc_id, ConversionFailureNonTypeToFacet, target.is_explicit_as(), |
| 1448 | + loc_id, ConversionFailureNonTypeToFacet, target_kind_for_diag, |
1379 | 1449 | expr_id, target.type_id == SemIR::TypeType::TypeId, target.type_id);
|
1380 | 1450 | } 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); |
1386 | 1456 | return context.emitter().Build(loc_id, ConversionFailure,
|
1387 |
| - target.is_explicit_as(), expr_id, |
| 1457 | + target_kind_for_diag, expr_id, |
1388 | 1458 | target.type_id);
|
1389 | 1459 | }
|
1390 | 1460 | });
|
|
0 commit comments