Skip to content

Commit 50e5476

Browse files
authored
Add support for unsafe as operator to the toolchain. (#5993)
Following the direction of #5913, add support for parsing an `unsafe as` operator. For now, we allow one additional conversion using `unsafe as` beyond the conversions supported by `as`: we permit pointer conversions that remove qualifiers, such as `const T*` -> `T*`.
1 parent 1d0f30d commit 50e5476

File tree

20 files changed

+330
-57
lines changed

20 files changed

+330
-57
lines changed

core/prelude/operators/as.carbon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
package Core library "prelude/operators/as";
66

7+
interface UnsafeAs(Dest:! type) {
8+
fn Convert[self: Self]() -> Dest;
9+
}
10+
711
interface As(Dest:! type) {
12+
// TODO: extend UnsafeAs(Dest);
813
fn Convert[self: Self]() -> Dest;
914
}
1015

toolchain/check/convert.cpp

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ static auto IsValidExprCategoryForConversionTarget(
731731
case ConversionTarget::CppThunkRef:
732732
return category == SemIR::ExprCategory::EphemeralRef;
733733
case ConversionTarget::ExplicitAs:
734+
case ConversionTarget::ExplicitUnsafeAs:
734735
return true;
735736
case ConversionTarget::Initializer:
736737
case ConversionTarget::FullInitializer:
@@ -914,8 +915,7 @@ static auto PerformBuiltinConversion(
914915
}
915916

916917
// T explicitly converts to U if T is compatible with U.
917-
if (target.kind == ConversionTarget::Kind::ExplicitAs &&
918-
target.type_id != value_type_id) {
918+
if (target.is_explicit_as() && target.type_id != value_type_id) {
919919
auto target_foundation_id =
920920
context.types().GetTransitiveAdaptedType(target.type_id);
921921
auto value_foundation_id =
@@ -1010,10 +1010,12 @@ static auto PerformBuiltinConversion(
10101010
context.types().GetTypeIdForTypeInstId(
10111011
src_pointer_type->pointee_id));
10121012

1013-
// If the qualifiers are incompatible, we can't perform a conversion.
1014-
if ((src_quals & ~target_quals) != SemIR::TypeQualifiers::None) {
1013+
// If the qualifiers are incompatible, we can't perform a conversion,
1014+
// except with `unsafe as`.
1015+
if ((src_quals & ~target_quals) != SemIR::TypeQualifiers::None &&
1016+
target.kind != ConversionTarget::ExplicitUnsafeAs) {
10151017
// TODO: Consider producing a custom diagnostic here for a cast that
1016-
// discards constness. We should allow this with `unsafe as`.
1018+
// discards constness.
10171019
return value_id;
10181020
}
10191021

@@ -1231,6 +1233,19 @@ static auto ConvertValueForCppThunkRef(Context& context, SemIR::InstId expr_id,
12311233
return expr_id;
12321234
}
12331235

1236+
// Returns the Core interface name to use for a given kind of conversion.
1237+
static auto GetConversionInterfaceName(ConversionTarget::Kind kind)
1238+
-> llvm::StringLiteral {
1239+
switch (kind) {
1240+
case ConversionTarget::ExplicitAs:
1241+
return "As";
1242+
case ConversionTarget::ExplicitUnsafeAs:
1243+
return "UnsafeAs";
1244+
default:
1245+
return "ImplicitAs";
1246+
}
1247+
}
1248+
12341249
auto PerformAction(Context& context, SemIR::LocId loc_id,
12351250
SemIR::ConvertToValueAction action) -> SemIR::InstId {
12361251
return Convert(context, loc_id, action.inst_id,
@@ -1344,9 +1359,7 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
13441359
SemIR::InstId interface_args[] = {
13451360
context.types().GetInstId(target.type_id)};
13461361
Operator op = {
1347-
.interface_name = target.kind == ConversionTarget::ExplicitAs
1348-
? llvm::StringLiteral("As")
1349-
: llvm::StringLiteral("ImplicitAs"),
1362+
.interface_name = GetConversionInterfaceName(target.kind),
13501363
.interface_args_ref = interface_args,
13511364
.op_name = "Convert",
13521365
};
@@ -1363,19 +1376,17 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
13631376
Diagnostics::BoolAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect,
13641377
SemIR::TypeId);
13651378
return context.emitter().Build(
1366-
loc_id, ConversionFailureNonTypeToFacet,
1367-
target.kind == ConversionTarget::ExplicitAs, expr_id,
1368-
target.type_id == SemIR::TypeType::TypeId, target.type_id);
1379+
loc_id, ConversionFailureNonTypeToFacet, target.is_explicit_as(),
1380+
expr_id, target.type_id == SemIR::TypeType::TypeId, target.type_id);
13691381
} else {
13701382
CARBON_DIAGNOSTIC(ConversionFailure, Error,
13711383
"cannot{0:| implicitly} convert expression of type "
13721384
"{1} to {2}{0: with `as`|}",
13731385
Diagnostics::BoolAsSelect, TypeOfInstId,
13741386
SemIR::TypeId);
1375-
return context.emitter().Build(
1376-
loc_id, ConversionFailure,
1377-
target.kind == ConversionTarget::ExplicitAs, expr_id,
1378-
target.type_id);
1387+
return context.emitter().Build(loc_id, ConversionFailure,
1388+
target.is_explicit_as(), expr_id,
1389+
target.type_id);
13791390
}
13801391
});
13811392

@@ -1397,7 +1408,7 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
13971408

13981409
// For `as`, don't perform any value category conversions. In particular, an
13991410
// identity conversion shouldn't change the expression category.
1400-
if (target.kind == ConversionTarget::ExplicitAs) {
1411+
if (target.is_explicit_as()) {
14011412
return expr_id;
14021413
}
14031414

@@ -1554,10 +1565,12 @@ auto ConvertToBoolValue(Context& context, SemIR::LocId loc_id,
15541565
}
15551566

15561567
auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node,
1557-
SemIR::InstId value_id, SemIR::TypeId type_id)
1558-
-> SemIR::InstId {
1568+
SemIR::InstId value_id, SemIR::TypeId type_id,
1569+
bool unsafe) -> SemIR::InstId {
15591570
return Convert(context, as_node, value_id,
1560-
{.kind = ConversionTarget::ExplicitAs, .type_id = type_id});
1571+
{.kind = unsafe ? ConversionTarget::ExplicitUnsafeAs
1572+
: ConversionTarget::ExplicitAs,
1573+
.type_id = type_id});
15611574
}
15621575

15631576
// TODO: Consider moving this to pattern_match.h.

toolchain/check/convert.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ struct ConversionTarget {
2828
// as the result, and uses the `As` interface instead of the `ImplicitAs`
2929
// interface.
3030
ExplicitAs,
31+
// Convert for an explicit `unsafe as` cast. This allows any expression
32+
// category as the result, and uses the `UnsafeAs` interface instead of the
33+
// `As` or `ImplicitAs` interface.
34+
ExplicitUnsafeAs,
3135
// The result of the conversion is discarded. It can't be an initializing
3236
// expression, but can be anything else.
3337
Discarded,
@@ -57,6 +61,10 @@ struct ConversionTarget {
5761
auto is_initializer() const -> bool {
5862
return kind == Initializer || kind == FullInitializer;
5963
}
64+
// Is this some kind of explicit `as` conversion?
65+
auto is_explicit_as() const -> bool {
66+
return kind == ExplicitAs || kind == ExplicitUnsafeAs;
67+
}
6068
};
6169

6270
// Convert a value to another type and expression category.
@@ -108,8 +116,8 @@ auto ConvertToBoolValue(Context& context, SemIR::LocId loc_id,
108116

109117
// Converts `value_id` to type `type_id` for an `as` expression.
110118
auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node,
111-
SemIR::InstId value_id, SemIR::TypeId type_id)
112-
-> SemIR::InstId;
119+
SemIR::InstId value_id, SemIR::TypeId type_id,
120+
bool unsafe) -> SemIR::InstId;
113121

114122
// Implicitly converts a set of arguments to match the parameter types in a
115123
// function call. Returns a block containing the converted implicit and explicit

toolchain/check/handle_operator.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "toolchain/check/pointer_dereference.h"
1212
#include "toolchain/check/type.h"
1313
#include "toolchain/diagnostics/diagnostic_emitter.h"
14+
#include "toolchain/parse/typed_nodes.h"
1415
#include "toolchain/sem_ir/expr_info.h"
1516

1617
namespace Carbon::Check {
@@ -53,14 +54,25 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorAmpEqualId node_id)
5354
return HandleBinaryOperator(context, node_id, {"BitAndAssignWith"});
5455
}
5556

57+
auto HandleParseNode(Context& context, Parse::UnsafeModifierId node_id)
58+
-> bool {
59+
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
60+
context.node_stack().Push(node_id, rhs_id);
61+
return true;
62+
}
63+
5664
auto HandleParseNode(Context& context, Parse::InfixOperatorAsId node_id)
5765
-> bool {
5866
auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId();
5967
auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId();
6068

69+
bool unsafe = context.parse_tree().node_kind(lhs_node) ==
70+
Parse::NodeKind::UnsafeModifier;
71+
6172
auto rhs_type_id = ExprAsType(context, rhs_node, rhs_id).type_id;
6273
context.node_stack().Push(
63-
node_id, ConvertForExplicitAs(context, node_id, lhs_id, rhs_type_id));
74+
node_id,
75+
ConvertForExplicitAs(context, node_id, lhs_id, rhs_type_id, unsafe));
6476
return true;
6577
}
6678

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon
6+
//
7+
// AUTOUPDATE
8+
// TIP: To test this file alone, run:
9+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/as/unsafe_as.carbon
10+
// TIP: To dump output, run:
11+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/unsafe_as.carbon
12+
13+
// --- qualifiers.carbon
14+
15+
library "[[@TEST_NAME]]";
16+
17+
class X {}
18+
19+
fn Convert(p: const X*) -> X* {
20+
//@dump-sem-ir-begin
21+
return p unsafe as X*;
22+
//@dump-sem-ir-end
23+
}
24+
25+
// --- fail_no_conversion.carbon
26+
27+
library "[[@TEST_NAME]]";
28+
29+
class A {};
30+
class B {};
31+
32+
fn Convert(a: A) -> B {
33+
// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `A` to `B` with `as` [ConversionFailure]
34+
// CHECK:STDERR: return a unsafe as B;
35+
// CHECK:STDERR: ^~~~~~~~~~~~~
36+
// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:10: note: type `A` does not implement interface `Core.UnsafeAs(B)` [MissingImplInMemberAccessNote]
37+
// CHECK:STDERR: return a unsafe as B;
38+
// CHECK:STDERR: ^~~~~~~~~~~~~
39+
// CHECK:STDERR:
40+
return a unsafe as B;
41+
}
42+
43+
// --- fail_remove_qualifiers_without_unsafe.carbon
44+
45+
library "[[@TEST_NAME]]";
46+
47+
class X {}
48+
49+
fn Convert(p: const X*) -> X* {
50+
// CHECK:STDERR: fail_remove_qualifiers_without_unsafe.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `const X*` to `X*` with `as` [ConversionFailure]
51+
// CHECK:STDERR: return p as X*;
52+
// CHECK:STDERR: ^~~~~~~
53+
// CHECK:STDERR: fail_remove_qualifiers_without_unsafe.carbon:[[@LINE+4]]:10: note: type `const X*` does not implement interface `Core.As(X*)` [MissingImplInMemberAccessNote]
54+
// CHECK:STDERR: return p as X*;
55+
// CHECK:STDERR: ^~~~~~~
56+
// CHECK:STDERR:
57+
return p as X*;
58+
}
59+
60+
// CHECK:STDOUT: --- qualifiers.carbon
61+
// CHECK:STDOUT:
62+
// CHECK:STDOUT: constants {
63+
// CHECK:STDOUT: %X: type = class_type @X [concrete]
64+
// CHECK:STDOUT: %ptr.d17: type = ptr_type %X [concrete]
65+
// CHECK:STDOUT: %const: type = const_type %X [concrete]
66+
// CHECK:STDOUT: %ptr.cbd: type = ptr_type %const [concrete]
67+
// CHECK:STDOUT: }
68+
// CHECK:STDOUT:
69+
// CHECK:STDOUT: imports {
70+
// CHECK:STDOUT: }
71+
// CHECK:STDOUT:
72+
// CHECK:STDOUT: fn @Convert(%p.param: %ptr.cbd) -> %ptr.d17 {
73+
// CHECK:STDOUT: !entry:
74+
// CHECK:STDOUT: %p.ref: %ptr.cbd = name_ref p, %p
75+
// CHECK:STDOUT: %X.ref.loc8: type = name_ref X, file.%X.decl [concrete = constants.%X]
76+
// CHECK:STDOUT: %ptr.loc8: type = ptr_type %X.ref.loc8 [concrete = constants.%ptr.d17]
77+
// CHECK:STDOUT: %.loc8_19.1: %ptr.d17 = as_compatible %p.ref
78+
// CHECK:STDOUT: %.loc8_19.2: %ptr.d17 = converted %p.ref, %.loc8_19.1
79+
// CHECK:STDOUT: return %.loc8_19.2
80+
// CHECK:STDOUT: }
81+
// CHECK:STDOUT:

toolchain/check/testdata/deduce/binding_pattern.carbon

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ fn F(U:! type, V:! type where {} impls Core.ImplicitAs(.Self)) {
113113
// CHECK:STDOUT: }
114114
// CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
115115
// CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic]
116-
// CHECK:STDOUT: %Core.import_ref.492: @ImplicitAs.%ImplicitAs.assoc_type (%ImplicitAs.assoc_type.ca0) = import_ref Core//prelude/parts/as, loc12_35, loaded [symbolic = @ImplicitAs.%assoc0 (constants.%assoc0.dc0)]
117-
// CHECK:STDOUT: %Core.import_ref.1c7: @ImplicitAs.%ImplicitAs.Convert.type (%ImplicitAs.Convert.type.275) = import_ref Core//prelude/parts/as, loc12_35, loaded [symbolic = @ImplicitAs.%ImplicitAs.Convert (constants.%ImplicitAs.Convert.42e)]
118-
// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc12_35, unloaded
116+
// CHECK:STDOUT: %Core.import_ref.492: @ImplicitAs.%ImplicitAs.assoc_type (%ImplicitAs.assoc_type.ca0) = import_ref Core//prelude/parts/as, loc17_35, loaded [symbolic = @ImplicitAs.%assoc0 (constants.%assoc0.dc0)]
117+
// CHECK:STDOUT: %Core.import_ref.1c7: @ImplicitAs.%ImplicitAs.Convert.type (%ImplicitAs.Convert.type.275) = import_ref Core//prelude/parts/as, loc17_35, loaded [symbolic = @ImplicitAs.%ImplicitAs.Convert (constants.%ImplicitAs.Convert.42e)]
118+
// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc17_35, unloaded
119119
// CHECK:STDOUT: }
120120
// CHECK:STDOUT:
121121
// CHECK:STDOUT: file {
@@ -366,9 +366,9 @@ fn F(U:! type, V:! type where {} impls Core.ImplicitAs(.Self)) {
366366
// CHECK:STDOUT: }
367367
// CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type]
368368
// CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic]
369-
// CHECK:STDOUT: %Core.import_ref.492: @ImplicitAs.%ImplicitAs.assoc_type (%ImplicitAs.assoc_type.ca0) = import_ref Core//prelude/parts/as, loc12_35, loaded [symbolic = @ImplicitAs.%assoc0 (constants.%assoc0.dc0)]
370-
// CHECK:STDOUT: %Core.import_ref.1c7: @ImplicitAs.%ImplicitAs.Convert.type (%ImplicitAs.Convert.type.275) = import_ref Core//prelude/parts/as, loc12_35, loaded [symbolic = @ImplicitAs.%ImplicitAs.Convert (constants.%ImplicitAs.Convert.42e)]
371-
// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc12_35, unloaded
369+
// CHECK:STDOUT: %Core.import_ref.492: @ImplicitAs.%ImplicitAs.assoc_type (%ImplicitAs.assoc_type.ca0) = import_ref Core//prelude/parts/as, loc17_35, loaded [symbolic = @ImplicitAs.%assoc0 (constants.%assoc0.dc0)]
370+
// CHECK:STDOUT: %Core.import_ref.1c7: @ImplicitAs.%ImplicitAs.Convert.type (%ImplicitAs.Convert.type.275) = import_ref Core//prelude/parts/as, loc17_35, loaded [symbolic = @ImplicitAs.%ImplicitAs.Convert (constants.%ImplicitAs.Convert.42e)]
371+
// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc17_35, unloaded
372372
// CHECK:STDOUT: }
373373
// CHECK:STDOUT:
374374
// CHECK:STDOUT: file {

0 commit comments

Comments
 (0)