From 50e5476ee32c9d9bf27e48567315d43b1f188772 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 28 Aug 2025 16:00:53 -0700 Subject: [PATCH] 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*`. --- core/prelude/operators/as.carbon | 5 + toolchain/check/convert.cpp | 51 +++++---- toolchain/check/convert.h | 12 +- toolchain/check/handle_operator.cpp | 14 ++- toolchain/check/testdata/as/unsafe_as.carbon | 81 ++++++++++++++ .../testdata/deduce/binding_pattern.carbon | 12 +- toolchain/check/testdata/for/actual.carbon | 24 ++-- ...pecialization_with_symbolic_rewrite.carbon | 6 +- .../check/testdata/let/fail_generic.carbon | 6 +- toolchain/diagnostics/diagnostic_kind.def | 3 + toolchain/lex/token_kind.def | 1 + toolchain/lower/testdata/array/iterate.carbon | 4 +- .../lower/testdata/for/break_continue.carbon | 4 +- toolchain/lower/testdata/for/for.carbon | 4 +- .../lower/testdata/operators/increment.carbon | 4 +- toolchain/parse/handle_expr.cpp | 35 +++++- toolchain/parse/node_kind.def | 2 + .../parse/testdata/operators/modifier.carbon | 104 ++++++++++++++++++ toolchain/parse/typed_nodes.h | 10 ++ .../testdata/min_prelude/parts/as.carbon | 5 + 20 files changed, 330 insertions(+), 57 deletions(-) create mode 100644 toolchain/check/testdata/as/unsafe_as.carbon create mode 100644 toolchain/parse/testdata/operators/modifier.carbon diff --git a/core/prelude/operators/as.carbon b/core/prelude/operators/as.carbon index 362e56205bff..86795df43ad0 100644 --- a/core/prelude/operators/as.carbon +++ b/core/prelude/operators/as.carbon @@ -4,7 +4,12 @@ package Core library "prelude/operators/as"; +interface UnsafeAs(Dest:! type) { + fn Convert[self: Self]() -> Dest; +} + interface As(Dest:! type) { + // TODO: extend UnsafeAs(Dest); fn Convert[self: Self]() -> Dest; } diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index d1b54fea74f4..306f42bd2c4d 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -731,6 +731,7 @@ static auto IsValidExprCategoryForConversionTarget( case ConversionTarget::CppThunkRef: return category == SemIR::ExprCategory::EphemeralRef; case ConversionTarget::ExplicitAs: + case ConversionTarget::ExplicitUnsafeAs: return true; case ConversionTarget::Initializer: case ConversionTarget::FullInitializer: @@ -914,8 +915,7 @@ static auto PerformBuiltinConversion( } // T explicitly converts to U if T is compatible with U. - if (target.kind == ConversionTarget::Kind::ExplicitAs && - target.type_id != value_type_id) { + 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 = @@ -1010,10 +1010,12 @@ static auto PerformBuiltinConversion( context.types().GetTypeIdForTypeInstId( src_pointer_type->pointee_id)); - // If the qualifiers are incompatible, we can't perform a conversion. - if ((src_quals & ~target_quals) != SemIR::TypeQualifiers::None) { + // If the qualifiers are incompatible, we can't perform a conversion, + // except with `unsafe as`. + if ((src_quals & ~target_quals) != SemIR::TypeQualifiers::None && + target.kind != ConversionTarget::ExplicitUnsafeAs) { // TODO: Consider producing a custom diagnostic here for a cast that - // discards constness. We should allow this with `unsafe as`. + // discards constness. return value_id; } @@ -1231,6 +1233,19 @@ static auto ConvertValueForCppThunkRef(Context& context, SemIR::InstId expr_id, return expr_id; } +// Returns the Core interface name to use for a given kind of conversion. +static auto GetConversionInterfaceName(ConversionTarget::Kind kind) + -> llvm::StringLiteral { + switch (kind) { + case ConversionTarget::ExplicitAs: + return "As"; + case ConversionTarget::ExplicitUnsafeAs: + return "UnsafeAs"; + default: + return "ImplicitAs"; + } +} + auto PerformAction(Context& context, SemIR::LocId loc_id, SemIR::ConvertToValueAction action) -> SemIR::InstId { return Convert(context, loc_id, action.inst_id, @@ -1344,9 +1359,7 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, SemIR::InstId interface_args[] = { context.types().GetInstId(target.type_id)}; Operator op = { - .interface_name = target.kind == ConversionTarget::ExplicitAs - ? llvm::StringLiteral("As") - : llvm::StringLiteral("ImplicitAs"), + .interface_name = GetConversionInterfaceName(target.kind), .interface_args_ref = interface_args, .op_name = "Convert", }; @@ -1363,19 +1376,17 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, Diagnostics::BoolAsSelect, TypeOfInstId, Diagnostics::BoolAsSelect, SemIR::TypeId); return context.emitter().Build( - loc_id, ConversionFailureNonTypeToFacet, - target.kind == ConversionTarget::ExplicitAs, expr_id, - target.type_id == SemIR::TypeType::TypeId, target.type_id); + loc_id, ConversionFailureNonTypeToFacet, target.is_explicit_as(), + 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); - return context.emitter().Build( - loc_id, ConversionFailure, - target.kind == ConversionTarget::ExplicitAs, expr_id, - target.type_id); + return context.emitter().Build(loc_id, ConversionFailure, + target.is_explicit_as(), expr_id, + target.type_id); } }); @@ -1397,7 +1408,7 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id, // For `as`, don't perform any value category conversions. In particular, an // identity conversion shouldn't change the expression category. - if (target.kind == ConversionTarget::ExplicitAs) { + if (target.is_explicit_as()) { return expr_id; } @@ -1554,10 +1565,12 @@ auto ConvertToBoolValue(Context& context, SemIR::LocId loc_id, } auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node, - SemIR::InstId value_id, SemIR::TypeId type_id) - -> SemIR::InstId { + SemIR::InstId value_id, SemIR::TypeId type_id, + bool unsafe) -> SemIR::InstId { return Convert(context, as_node, value_id, - {.kind = ConversionTarget::ExplicitAs, .type_id = type_id}); + {.kind = unsafe ? ConversionTarget::ExplicitUnsafeAs + : ConversionTarget::ExplicitAs, + .type_id = type_id}); } // TODO: Consider moving this to pattern_match.h. diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 33e807bf15d4..56add3fb46ef 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -28,6 +28,10 @@ struct ConversionTarget { // as the result, and uses the `As` interface instead of the `ImplicitAs` // interface. ExplicitAs, + // Convert for an explicit `unsafe as` cast. This allows any expression + // category as the result, and uses the `UnsafeAs` interface instead of the + // `As` or `ImplicitAs` interface. + ExplicitUnsafeAs, // The result of the conversion is discarded. It can't be an initializing // expression, but can be anything else. Discarded, @@ -57,6 +61,10 @@ struct ConversionTarget { auto is_initializer() const -> bool { return kind == Initializer || kind == FullInitializer; } + // Is this some kind of explicit `as` conversion? + auto is_explicit_as() const -> bool { + return kind == ExplicitAs || kind == ExplicitUnsafeAs; + } }; // Convert a value to another type and expression category. @@ -108,8 +116,8 @@ auto ConvertToBoolValue(Context& context, SemIR::LocId loc_id, // Converts `value_id` to type `type_id` for an `as` expression. auto ConvertForExplicitAs(Context& context, Parse::NodeId as_node, - SemIR::InstId value_id, SemIR::TypeId type_id) - -> SemIR::InstId; + SemIR::InstId value_id, SemIR::TypeId type_id, + bool unsafe) -> SemIR::InstId; // Implicitly converts a set of arguments to match the parameter types in a // function call. Returns a block containing the converted implicit and explicit diff --git a/toolchain/check/handle_operator.cpp b/toolchain/check/handle_operator.cpp index 7e4caf2ebf98..11dfe14ea226 100644 --- a/toolchain/check/handle_operator.cpp +++ b/toolchain/check/handle_operator.cpp @@ -11,6 +11,7 @@ #include "toolchain/check/pointer_dereference.h" #include "toolchain/check/type.h" #include "toolchain/diagnostics/diagnostic_emitter.h" +#include "toolchain/parse/typed_nodes.h" #include "toolchain/sem_ir/expr_info.h" namespace Carbon::Check { @@ -53,14 +54,25 @@ auto HandleParseNode(Context& context, Parse::InfixOperatorAmpEqualId node_id) return HandleBinaryOperator(context, node_id, {"BitAndAssignWith"}); } +auto HandleParseNode(Context& context, Parse::UnsafeModifierId node_id) + -> bool { + auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId(); + context.node_stack().Push(node_id, rhs_id); + return true; +} + auto HandleParseNode(Context& context, Parse::InfixOperatorAsId node_id) -> bool { auto [rhs_node, rhs_id] = context.node_stack().PopExprWithNodeId(); auto [lhs_node, lhs_id] = context.node_stack().PopExprWithNodeId(); + bool unsafe = context.parse_tree().node_kind(lhs_node) == + Parse::NodeKind::UnsafeModifier; + auto rhs_type_id = ExprAsType(context, rhs_node, rhs_id).type_id; context.node_stack().Push( - node_id, ConvertForExplicitAs(context, node_id, lhs_id, rhs_type_id)); + node_id, + ConvertForExplicitAs(context, node_id, lhs_id, rhs_type_id, unsafe)); return true; } diff --git a/toolchain/check/testdata/as/unsafe_as.carbon b/toolchain/check/testdata/as/unsafe_as.carbon new file mode 100644 index 000000000000..7884b5a0c921 --- /dev/null +++ b/toolchain/check/testdata/as/unsafe_as.carbon @@ -0,0 +1,81 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/as/unsafe_as.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/as/unsafe_as.carbon + +// --- qualifiers.carbon + +library "[[@TEST_NAME]]"; + +class X {} + +fn Convert(p: const X*) -> X* { + //@dump-sem-ir-begin + return p unsafe as X*; + //@dump-sem-ir-end +} + +// --- fail_no_conversion.carbon + +library "[[@TEST_NAME]]"; + +class A {}; +class B {}; + +fn Convert(a: A) -> B { + // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `A` to `B` with `as` [ConversionFailure] + // CHECK:STDERR: return a unsafe as B; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:10: note: type `A` does not implement interface `Core.UnsafeAs(B)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: return a unsafe as B; + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + return a unsafe as B; +} + +// --- fail_remove_qualifiers_without_unsafe.carbon + +library "[[@TEST_NAME]]"; + +class X {} + +fn Convert(p: const X*) -> X* { + // CHECK:STDERR: fail_remove_qualifiers_without_unsafe.carbon:[[@LINE+7]]:10: error: cannot convert expression of type `const X*` to `X*` with `as` [ConversionFailure] + // CHECK:STDERR: return p as X*; + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: fail_remove_qualifiers_without_unsafe.carbon:[[@LINE+4]]:10: note: type `const X*` does not implement interface `Core.As(X*)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: return p as X*; + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: + return p as X*; +} + +// CHECK:STDOUT: --- qualifiers.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %X: type = class_type @X [concrete] +// CHECK:STDOUT: %ptr.d17: type = ptr_type %X [concrete] +// CHECK:STDOUT: %const: type = const_type %X [concrete] +// CHECK:STDOUT: %ptr.cbd: type = ptr_type %const [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Convert(%p.param: %ptr.cbd) -> %ptr.d17 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %p.ref: %ptr.cbd = name_ref p, %p +// CHECK:STDOUT: %X.ref.loc8: type = name_ref X, file.%X.decl [concrete = constants.%X] +// CHECK:STDOUT: %ptr.loc8: type = ptr_type %X.ref.loc8 [concrete = constants.%ptr.d17] +// CHECK:STDOUT: %.loc8_19.1: %ptr.d17 = as_compatible %p.ref +// CHECK:STDOUT: %.loc8_19.2: %ptr.d17 = converted %p.ref, %.loc8_19.1 +// CHECK:STDOUT: return %.loc8_19.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/deduce/binding_pattern.carbon b/toolchain/check/testdata/deduce/binding_pattern.carbon index 2387bcfa958a..f53b9a0e9cc5 100644 --- a/toolchain/check/testdata/deduce/binding_pattern.carbon +++ b/toolchain/check/testdata/deduce/binding_pattern.carbon @@ -113,9 +113,9 @@ fn F(U:! type, V:! type where {} impls Core.ImplicitAs(.Self)) { // CHECK:STDOUT: } // CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] // CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] -// 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)] -// 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)] -// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc12_35, unloaded +// 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)] +// 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)] +// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc17_35, unloaded // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -366,9 +366,9 @@ fn F(U:! type, V:! type where {} impls Core.ImplicitAs(.Self)) { // CHECK:STDOUT: } // CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] // CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] -// 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)] -// 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)] -// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc12_35, unloaded +// 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)] +// 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)] +// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc17_35, unloaded // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { diff --git a/toolchain/check/testdata/for/actual.carbon b/toolchain/check/testdata/for/actual.carbon index 3010b8a6af88..9bface14c648 100644 --- a/toolchain/check/testdata/for/actual.carbon +++ b/toolchain/check/testdata/for/actual.carbon @@ -220,28 +220,28 @@ fn Read() { // CHECK:STDOUT: %Core.import_ref.1c9: %Iterate.assoc_type = import_ref Core//prelude/iterate, loc12_18, loaded [concrete = constants.%assoc0.724] // CHECK:STDOUT: %Core.import_ref.ed6: %Iterate.assoc_type = import_ref Core//prelude/iterate, loc13_17, loaded [concrete = constants.%assoc1.02e] // CHECK:STDOUT: %Core.import_ref.9e6: type = import_ref Core//prelude/iterate, loc13_17, loaded [concrete = %CursorType] -// CHECK:STDOUT: %Core.import_ref.f49: @Optional.%Optional.None.type (%Optional.None.type.ef2) = import_ref Core//prelude/iterate, inst138 [indirect], loaded [symbolic = @Optional.%Optional.None (constants.%Optional.None.fd6)] -// CHECK:STDOUT: %Core.import_ref.1a8: @Optional.%Optional.Some.type (%Optional.Some.type.b2c) = import_ref Core//prelude/iterate, inst139 [indirect], loaded [symbolic = @Optional.%Optional.Some (constants.%Optional.Some.d0d)] -// CHECK:STDOUT: %Core.import_ref.36a9: @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op.type (%Optional.as.Destroy.impl.Op.type.764) = import_ref Core//prelude/iterate, inst6889 [indirect], loaded [symbolic = @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op (constants.%Optional.as.Destroy.impl.Op.bf8)] +// CHECK:STDOUT: %Core.import_ref.f49: @Optional.%Optional.None.type (%Optional.None.type.ef2) = import_ref Core//prelude/iterate, inst139 [indirect], loaded [symbolic = @Optional.%Optional.None (constants.%Optional.None.fd6)] +// CHECK:STDOUT: %Core.import_ref.1a8: @Optional.%Optional.Some.type (%Optional.Some.type.b2c) = import_ref Core//prelude/iterate, inst140 [indirect], loaded [symbolic = @Optional.%Optional.Some (constants.%Optional.Some.d0d)] +// CHECK:STDOUT: %Core.import_ref.36a9: @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op.type (%Optional.as.Destroy.impl.Op.type.764) = import_ref Core//prelude/iterate, inst6890 [indirect], loaded [symbolic = @Optional.as.Destroy.impl.%Optional.as.Destroy.impl.Op (constants.%Optional.as.Destroy.impl.Op.bf8)] // CHECK:STDOUT: %Destroy.impl_witness_table.2ff = impl_witness_table (%Core.import_ref.36a9), @Optional.as.Destroy.impl [concrete] -// CHECK:STDOUT: %Core.import_ref.cf4: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.0f9) = import_ref Core//prelude/iterate, inst482 [indirect], loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f06)] +// CHECK:STDOUT: %Core.import_ref.cf4: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.0f9) = import_ref Core//prelude/iterate, inst483 [indirect], loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f06)] // CHECK:STDOUT: %ImplicitAs.impl_witness_table.2b9 = impl_witness_table (%Core.import_ref.cf4), @Core.IntLiteral.as.ImplicitAs.impl [concrete] -// CHECK:STDOUT: %Core.import_ref.741: @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op.type (%Int.as.Destroy.impl.Op.type) = import_ref Core//prelude/iterate, inst450 [indirect], loaded [symbolic = @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op (constants.%Int.as.Destroy.impl.Op)] +// CHECK:STDOUT: %Core.import_ref.741: @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op.type (%Int.as.Destroy.impl.Op.type) = import_ref Core//prelude/iterate, inst451 [indirect], loaded [symbolic = @Int.as.Destroy.impl.%Int.as.Destroy.impl.Op (constants.%Int.as.Destroy.impl.Op)] // CHECK:STDOUT: %Destroy.impl_witness_table.1b4 = impl_witness_table (%Core.import_ref.741), @Int.as.Destroy.impl [concrete] -// CHECK:STDOUT: %Core.import_ref.19a: @OrderedWith.%OrderedWith.assoc_type (%OrderedWith.assoc_type.03c) = import_ref Core//prelude/iterate, inst862 [indirect], loaded [symbolic = @OrderedWith.%assoc0 (constants.%assoc0.5db)] -// CHECK:STDOUT: %Core.import_ref.b2b: @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less.type (%Int.as.OrderedWith.impl.Less.type.2c7) = import_ref Core//prelude/iterate, inst951 [indirect], loaded [symbolic = @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less (constants.%Int.as.OrderedWith.impl.Less.a5a)] -// CHECK:STDOUT: %Core.import_ref.ab6 = import_ref Core//prelude/iterate, inst952 [indirect], unloaded -// CHECK:STDOUT: %Core.import_ref.875 = import_ref Core//prelude/iterate, inst953 [indirect], unloaded -// CHECK:STDOUT: %Core.import_ref.82b = import_ref Core//prelude/iterate, inst954 [indirect], unloaded +// CHECK:STDOUT: %Core.import_ref.19a: @OrderedWith.%OrderedWith.assoc_type (%OrderedWith.assoc_type.03c) = import_ref Core//prelude/iterate, inst863 [indirect], loaded [symbolic = @OrderedWith.%assoc0 (constants.%assoc0.5db)] +// CHECK:STDOUT: %Core.import_ref.b2b: @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less.type (%Int.as.OrderedWith.impl.Less.type.2c7) = import_ref Core//prelude/iterate, inst952 [indirect], loaded [symbolic = @Int.as.OrderedWith.impl.db3.%Int.as.OrderedWith.impl.Less (constants.%Int.as.OrderedWith.impl.Less.a5a)] +// CHECK:STDOUT: %Core.import_ref.ab6 = import_ref Core//prelude/iterate, inst953 [indirect], unloaded +// CHECK:STDOUT: %Core.import_ref.875 = import_ref Core//prelude/iterate, inst954 [indirect], unloaded +// CHECK:STDOUT: %Core.import_ref.82b = import_ref Core//prelude/iterate, inst955 [indirect], unloaded // CHECK:STDOUT: %OrderedWith.impl_witness_table.476 = impl_witness_table (%Core.import_ref.b2b, %Core.import_ref.ab6, %Core.import_ref.875, %Core.import_ref.82b), @Int.as.OrderedWith.impl.db3 [concrete] -// CHECK:STDOUT: %Core.import_ref.13d: @OrderedWith.%OrderedWith.Less.type (%OrderedWith.Less.type.f19) = import_ref Core//prelude/iterate, inst1926 [indirect], loaded [symbolic = @OrderedWith.%OrderedWith.Less (constants.%OrderedWith.Less.02e)] +// CHECK:STDOUT: %Core.import_ref.13d: @OrderedWith.%OrderedWith.Less.type (%OrderedWith.Less.type.f19) = import_ref Core//prelude/iterate, inst1927 [indirect], loaded [symbolic = @OrderedWith.%OrderedWith.Less (constants.%OrderedWith.Less.02e)] // CHECK:STDOUT: %CursorType: type = assoc_const_decl @CursorType [concrete] {} // CHECK:STDOUT: %Core.import_ref.4f9: type = import_ref Core//prelude/iterate, loc12_18, loaded [concrete = %ElementType] // CHECK:STDOUT: %ElementType: type = assoc_const_decl @ElementType [concrete] {} // CHECK:STDOUT: %Core.Optional: %Optional.type = import_ref Core//prelude/types/optional, Optional, loaded [concrete = constants.%Optional.generic] // CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/destroy, Destroy, loaded [concrete = constants.%Destroy.type] // CHECK:STDOUT: %Core.OrderedWith: %OrderedWith.type.270 = import_ref Core//prelude/operators/comparison, OrderedWith, loaded [concrete = constants.%OrderedWith.generic] -// CHECK:STDOUT: %Core.import_ref.d49 = import_ref Core//prelude/iterate, inst6644 [indirect], unloaded +// CHECK:STDOUT: %Core.import_ref.d49 = import_ref Core//prelude/iterate, inst6645 [indirect], unloaded // CHECK:STDOUT: %Core.Inc: type = import_ref Core//prelude/operators/arithmetic, Inc, loaded [concrete = constants.%Inc.type] // CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/operators/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon b/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon index 2db09a4ecd24..5063dd5108e1 100644 --- a/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon +++ b/toolchain/check/testdata/impl/lookup/specialization_with_symbolic_rewrite.carbon @@ -534,9 +534,9 @@ fn F[T:! Ptr](var t: T) -> T.(Ptr.Type) { // CHECK:STDOUT: } // CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] // CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] -// 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)] -// 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)] -// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc12_35, unloaded +// 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)] +// 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)] +// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc17_35, unloaded // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { diff --git a/toolchain/check/testdata/let/fail_generic.carbon b/toolchain/check/testdata/let/fail_generic.carbon index ec054cd2cf20..7ae33b24e078 100644 --- a/toolchain/check/testdata/let/fail_generic.carbon +++ b/toolchain/check/testdata/let/fail_generic.carbon @@ -69,9 +69,9 @@ fn F(a: i32) -> i32 { // CHECK:STDOUT: } // CHECK:STDOUT: %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic] // CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.cc7 = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] -// CHECK:STDOUT: %Core.import_ref.492: @ImplicitAs.%ImplicitAs.assoc_type (%ImplicitAs.assoc_type.ca0d85.1) = import_ref Core//prelude/parts/as, loc12_35, loaded [symbolic = @ImplicitAs.%assoc0 (constants.%assoc0.dc0)] -// CHECK:STDOUT: %Core.import_ref.1c7: @ImplicitAs.%ImplicitAs.Convert.type (%ImplicitAs.Convert.type.2751f3.1) = import_ref Core//prelude/parts/as, loc12_35, loaded [symbolic = @ImplicitAs.%ImplicitAs.Convert (constants.%ImplicitAs.Convert.42ebb8.1)] -// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc12_35, unloaded +// CHECK:STDOUT: %Core.import_ref.492: @ImplicitAs.%ImplicitAs.assoc_type (%ImplicitAs.assoc_type.ca0d85.1) = import_ref Core//prelude/parts/as, loc17_35, loaded [symbolic = @ImplicitAs.%assoc0 (constants.%assoc0.dc0)] +// CHECK:STDOUT: %Core.import_ref.1c7: @ImplicitAs.%ImplicitAs.Convert.type (%ImplicitAs.Convert.type.2751f3.1) = import_ref Core//prelude/parts/as, loc17_35, loaded [symbolic = @ImplicitAs.%ImplicitAs.Convert (constants.%ImplicitAs.Convert.42ebb8.1)] +// CHECK:STDOUT: %Core.import_ref.207 = import_ref Core//prelude/parts/as, loc17_35, unloaded // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index d7de5b6a9d68..2ddab7b957e9 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -495,6 +495,9 @@ CARBON_DIAGNOSTIC_KIND(ModifierPrevious) CARBON_DIAGNOSTIC_KIND(ExternLibraryOnDefinition) CARBON_DIAGNOSTIC_KIND(ExternLibraryIsCurrentLibrary) +// Operator modifier checking. +CARBON_DIAGNOSTIC_KIND(ModifierNotAllowedOnOperator) + // Access modifiers. CARBON_DIAGNOSTIC_KIND(ClassMemberDeclaration) CARBON_DIAGNOSTIC_KIND(ClassInvalidMemberAccess) diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index 02bcc3ecb3a2..8f99e5d1b3a7 100644 --- a/toolchain/lex/token_kind.def +++ b/toolchain/lex/token_kind.def @@ -215,6 +215,7 @@ CARBON_KEYWORD_TOKEN(True, "true") CARBON_KEYWORD_TOKEN(Type, "type") // Underscore is tokenized as a keyword because it's part of identifiers. CARBON_KEYWORD_TOKEN(Underscore, "_") +CARBON_KEYWORD_TOKEN(Unsafe, "unsafe") CARBON_KEYWORD_TOKEN(Virtual, "virtual") CARBON_TOKEN_WITH_VIRTUAL_NODE( CARBON_KEYWORD_TOKEN(Where, "where")) diff --git a/toolchain/lower/testdata/array/iterate.carbon b/toolchain/lower/testdata/array/iterate.carbon index a9ebbfcc8d4f..50fc4cf91693 100644 --- a/toolchain/lower/testdata/array/iterate.carbon +++ b/toolchain/lower/testdata/array/iterate.carbon @@ -186,6 +186,6 @@ fn F() { // CHECK:STDOUT: !42 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk.Int.Core:AddAssignWith.Core.a6f9794233e6e547", scope: null, file: !33, line: 268, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !43 = !DILocation(line: 4294967295, scope: !42) // CHECK:STDOUT: !44 = !DILocation(line: 268, column: 3, scope: !42) -// CHECK:STDOUT: !45 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.1abd39b699024258", scope: null, file: !46, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !45 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.1abd39b699024258", scope: null, file: !46, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !46 = !DIFile(filename: "{{.*}}/prelude/operators/as.carbon", directory: "") -// CHECK:STDOUT: !47 = !DILocation(line: 18, column: 38, scope: !45) +// CHECK:STDOUT: !47 = !DILocation(line: 23, column: 38, scope: !45) diff --git a/toolchain/lower/testdata/for/break_continue.carbon b/toolchain/lower/testdata/for/break_continue.carbon index 7094c8bbd8af..0da9411e6597 100644 --- a/toolchain/lower/testdata/for/break_continue.carbon +++ b/toolchain/lower/testdata/for/break_continue.carbon @@ -215,6 +215,6 @@ fn For() { // CHECK:STDOUT: !51 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk.Int.Core:AddAssignWith.Core.abc639a111145970", scope: null, file: !42, line: 268, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !52 = !DILocation(line: 4294967295, scope: !51) // CHECK:STDOUT: !53 = !DILocation(line: 268, column: 3, scope: !51) -// CHECK:STDOUT: !54 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !55, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !54 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !55, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !55 = !DIFile(filename: "{{.*}}/prelude/operators/as.carbon", directory: "") -// CHECK:STDOUT: !56 = !DILocation(line: 18, column: 38, scope: !54) +// CHECK:STDOUT: !56 = !DILocation(line: 23, column: 38, scope: !54) diff --git a/toolchain/lower/testdata/for/for.carbon b/toolchain/lower/testdata/for/for.carbon index 7645570dd9fb..f2ab09df9371 100644 --- a/toolchain/lower/testdata/for/for.carbon +++ b/toolchain/lower/testdata/for/for.carbon @@ -199,6 +199,6 @@ fn For() { // CHECK:STDOUT: !47 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk.Int.Core:AddAssignWith.Core.abc639a111145970", scope: null, file: !38, line: 268, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !48 = !DILocation(line: 4294967295, scope: !47) // CHECK:STDOUT: !49 = !DILocation(line: 268, column: 3, scope: !47) -// CHECK:STDOUT: !50 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !51, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !50 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !51, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !51 = !DIFile(filename: "{{.*}}/prelude/operators/as.carbon", directory: "") -// CHECK:STDOUT: !52 = !DILocation(line: 18, column: 38, scope: !50) +// CHECK:STDOUT: !52 = !DILocation(line: 23, column: 38, scope: !50) diff --git a/toolchain/lower/testdata/operators/increment.carbon b/toolchain/lower/testdata/operators/increment.carbon index 1325b38aa549..87c19f2b33d8 100644 --- a/toolchain/lower/testdata/operators/increment.carbon +++ b/toolchain/lower/testdata/operators/increment.carbon @@ -73,6 +73,6 @@ fn IncrSigned() { // CHECK:STDOUT: !14 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk.Int.Core:AddAssignWith.Core.abc639a111145970", scope: null, file: !11, line: 268, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !15 = !DILocation(line: 4294967295, scope: !14) // CHECK:STDOUT: !16 = !DILocation(line: 268, column: 3, scope: !14) -// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !18, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !18, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2) // CHECK:STDOUT: !18 = !DIFile(filename: "{{.*}}/prelude/operators/as.carbon", directory: "") -// CHECK:STDOUT: !19 = !DILocation(line: 18, column: 38, scope: !17) +// CHECK:STDOUT: !19 = !DILocation(line: 23, column: 38, scope: !17) diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index 0a48c513da8a..f0455f7aae8f 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/toolchain/parse/handle_expr.cpp @@ -278,12 +278,24 @@ auto HandleExprLoop(Context& context) -> void { auto operator_kind = context.PositionKind(); auto trailing_operator = PrecedenceGroup::ForTrailing( operator_kind, context.IsTrailingOperatorInfix()); + if (!trailing_operator) { - if (state.has_error) { - context.ReturnErrorOnState(); + // TODO: Generalize this to handle a sequence of operator modifiers once we + // have more than one. + if (context.PositionIs(Lex::TokenKind::Unsafe)) { + operator_kind = context.PositionKind(Lookahead::NextToken); + trailing_operator = PrecedenceGroup::ForTrailing( + operator_kind, context.IsTrailingOperatorInfix()); + } + + if (!trailing_operator) { + if (state.has_error) { + context.ReturnErrorOnState(); + } + return; } - return; } + auto [operator_precedence, is_binary] = *trailing_operator; // TODO: If this operator is ambiguous with either the ambient precedence @@ -323,6 +335,23 @@ auto HandleExprLoop(Context& context) -> void { : Context::OperatorFixity::Postfix); } + // For operator modifiers, wrap the first operand in the modifier. + if (context.PositionIs(Lex::TokenKind::Unsafe)) { + if (context.PositionIs(Lex::TokenKind::As, Lookahead::NextToken)) { + context.AddNode(context.Consume(), + state.has_error); + } else { + CARBON_DIAGNOSTIC(ModifierNotAllowedOnOperator, Error, + "`{0}` not allowed on operator `{1}`", Lex::TokenKind, + Lex::TokenKind); + context.emitter().Emit(*context.position(), ModifierNotAllowedOnOperator, + context.PositionKind(), + context.PositionKind(Lookahead::NextToken)); + context.Consume(); + state.has_error = true; + } + } + state.token = context.Consume(); state.lhs_precedence = operator_precedence; diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 9ecaa3395e99..25ae8eada3a1 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -302,6 +302,8 @@ CARBON_PARSE_NODE_KIND(ShortCircuitOperandOr) CARBON_PARSE_NODE_KIND(ShortCircuitOperatorAnd) CARBON_PARSE_NODE_KIND(ShortCircuitOperatorOr) +CARBON_PARSE_NODE_KIND(UnsafeModifier) + CARBON_PARSE_NODE_KIND(DesignatorExpr) CARBON_PARSE_NODE_KIND(SelfTypeName) CARBON_PARSE_NODE_KIND(RequirementAnd) diff --git a/toolchain/parse/testdata/operators/modifier.carbon b/toolchain/parse/testdata/operators/modifier.carbon new file mode 100644 index 000000000000..80551cf5dc61 --- /dev/null +++ b/toolchain/parse/testdata/operators/modifier.carbon @@ -0,0 +1,104 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/operators/modifier.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/operators/modifier.carbon + +// --- valid.carbon + +fn F() { + a unsafe as b; +} + +// --- fail_invalid.carbon + +fn G() { + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:5: error: `unsafe` not allowed on operator `or` [ModifierNotAllowedOnOperator] + // CHECK:STDERR: a unsafe or b; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + a unsafe or b; + + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:5: error: `unsafe` not allowed on operator `*` [ModifierNotAllowedOnOperator] + // CHECK:STDERR: a unsafe * b; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + a unsafe * b; + + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:5: error: expected `;` after expression statement [ExpectedExprSemi] + // CHECK:STDERR: a unsafe; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + a unsafe; + + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:3: error: expected expression [ExpectedExpr] + // CHECK:STDERR: unsafe; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + unsafe; + + // TODO: It'd be nice to underline both tokens in `unsafe as` here. + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+8]]:9: error: parentheses are required to disambiguate operator precedence [OperatorRequiresParentheses] + // CHECK:STDERR: a + b unsafe as c * d; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_invalid.carbon:[[@LINE+4]]:21: error: parentheses are required to disambiguate operator precedence [OperatorRequiresParentheses] + // CHECK:STDERR: a + b unsafe as c * d; + // CHECK:STDERR: ^ + // CHECK:STDERR: + a + b unsafe as c * d; +} + +// CHECK:STDOUT: - filename: valid.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'F'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'UnsafeModifier', text: 'unsafe', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'InfixOperatorAs', text: 'as', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_invalid.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameBeforeParams', text: 'G'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'ShortCircuitOperandOr', text: 'or', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'ShortCircuitOperatorOr', text: 'or', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'InfixOperatorStar', text: '*', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: 'unsafe', has_error: yes}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'InfixOperatorPlus', text: '+', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'UnsafeModifier', text: 'unsafe', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'}, +// CHECK:STDOUT: {kind: 'InfixOperatorAs', text: 'as', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'd'}, +// CHECK:STDOUT: {kind: 'InfixOperatorStar', text: '*', has_error: yes, subtree_size: 8}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 28}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index d4411c349dcd..fc74f1b23b29 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -1036,6 +1036,16 @@ struct PostfixOperator { TokenKind token; }; +// An `unsafe` modifier: `a unsafe b`. This is modeled in the parse +// tree as a postfix operator applied to `a`. +struct UnsafeModifier { + static constexpr auto Kind = NodeKind::UnsafeModifier.Define( + {.category = NodeCategory::Expr, .child_count = 1}); + + AnyExprId operand; + Lex::UnsafeTokenIndex token; +}; + // Literals, operators, and modifiers #define CARBON_PARSE_NODE_KIND(Name) diff --git a/toolchain/testing/testdata/min_prelude/parts/as.carbon b/toolchain/testing/testdata/min_prelude/parts/as.carbon index 342a5543bf9d..198e8827481e 100644 --- a/toolchain/testing/testdata/min_prelude/parts/as.carbon +++ b/toolchain/testing/testdata/min_prelude/parts/as.carbon @@ -10,7 +10,12 @@ package Core library "prelude/parts/as"; export import library "prelude/parts/destroy"; +interface UnsafeAs(Dest:! type) { + fn Convert[self: Self]() -> Dest; +} + interface As(Dest:! type) { + // TODO: extend UnsafeAs(Dest); fn Convert[self: Self]() -> Dest; }