diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index 02b135f97123..c1e7f48bb710 100644 --- a/docs/design/lexical_conventions/words.md +++ b/docs/design/lexical_conventions/words.md @@ -99,6 +99,7 @@ The following words are interpreted as keywords: - `template` - `then` - `type` +- `typeof` - `var` - `virtual` - `where` diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 9f7ece42e6eb..d9d24ce436fe 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -3190,6 +3190,20 @@ auto TryEvalTypedInst(EvalContext& eval_context, return MakeConstantForCall(eval_context, inst_id, inst.As()); } +// `typeof` evaluates to the type of its operand. The operand is in a separate +// region that is not evaluated, so we look at the type of the region's result +// directly rather than evaluating any operands; this specialization avoids us +// needing a way to map a `ExprRegionId` to an evaluated version in a specific. +template <> +auto TryEvalTypedInst(EvalContext& eval_context, + SemIR::InstId /*inst_id*/, + SemIR::Inst inst) -> SemIR::ConstantId { + auto region = eval_context.sem_ir().expr_regions().Get( + inst.As().operand_region_id); + return eval_context.types().GetConstantId( + eval_context.GetTypeOfInst(region.result_id)); +} + // ImportRefLoaded can have a constant value, but it's owned and maintained by // `import_ref.cpp`, not by us. // TODO: Rearrange how `ImportRefLoaded` instructions are created so we never diff --git a/toolchain/check/handle_typeof.cpp b/toolchain/check/handle_typeof.cpp new file mode 100644 index 000000000000..0c8544bcf4ba --- /dev/null +++ b/toolchain/check/handle_typeof.cpp @@ -0,0 +1,48 @@ +// 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 "toolchain/check/context.h" +#include "toolchain/check/handle.h" +#include "toolchain/check/inst.h" + +namespace Carbon::Check { + +auto HandleParseNode(Context& /*context*/, + Parse::TypeOfExprKeywordId /*node_id*/) -> bool { + return true; +} + +auto HandleParseNode(Context& context, Parse::TypeOfExprOpenParenId /*node_id*/) + -> bool { + // The operand of `typeof` is never evaluated at runtime, so build it in a + // separate expression region that is not part of the enclosing control flow. + context.inst_block_stack().Push(); + context.region_stack().PushRegion(context.inst_block_stack().PeekOrAdd()); + // Any cleanups created by the operand are never needed. + context.scope_stack().PushForSameRegion(ScopeStack::CleanupScopeKind::None); + return true; +} + +auto HandleParseNode(Context& context, Parse::TypeOfExprId node_id) -> bool { + auto operand_id = context.node_stack().PopExpr(); + context.scope_stack().Pop(); + + // Finish building the operand region. Unlike regions that are later spliced + // into a control flow graph, this region has no successor, so we don't add a + // branch out of it. + auto block_id = context.inst_block_stack().Pop(); + CARBON_CHECK(block_id == context.region_stack().PeekRegion().back()); + auto operand_region_id = context.sem_ir().expr_regions().Add( + {.block_ids = context.region_stack().PopRegion(), + .result_id = operand_id}); + + auto inst_id = + AddInst(context, node_id, + {.type_id = SemIR::TypeType::TypeId, + .operand_region_id = operand_region_id}); + context.node_stack().Push(node_id, inst_id); + return true; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index 7210f9d04f69..b1c630231108 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -545,6 +545,8 @@ class NodeStack { case Parse::NodeKind::StructTypeLiteralComma: case Parse::NodeKind::TerseBodyArrow: case Parse::NodeKind::TupleLiteralComma: + case Parse::NodeKind::TypeOfExprKeyword: + case Parse::NodeKind::TypeOfExprOpenParen: case Parse::NodeKind::WhereOperand: case Parse::NodeKind::WhileCondition: return Id::Kind::Invalid; diff --git a/toolchain/check/testdata/typeof/basics.carbon b/toolchain/check/testdata/typeof/basics.carbon new file mode 100644 index 000000000000..1cb970c845a0 --- /dev/null +++ b/toolchain/check/testdata/typeof/basics.carbon @@ -0,0 +1,431 @@ +// 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/bool.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/typeof/basics.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/typeof/basics.carbon + +// --- basic.carbon + +library "[[@TEST_NAME]]"; + +class C {} + +var c: C = {}; + +//@dump-sem-ir-begin +var d: typeof(c) = {}; +//@dump-sem-ir-end + +// --- type_of_type.carbon + +library "[[@TEST_NAME]]"; + +class C {} + +//@dump-sem-ir-begin +let T: type = typeof(C); +//@dump-sem-ir-end + +// --- not_evaluated.carbon + +library "[[@TEST_NAME]]"; + +class C {} + +fn Make() -> C; + +fn F() { + //@dump-sem-ir-begin + var unused c: typeof(Make()) = {}; + //@dump-sem-ir-end +} + +// --- control_flow.carbon + +library "[[@TEST_NAME]]"; + +fn F(a: bool, b: bool) { + //@dump-sem-ir-begin + let unused c: typeof(a and b) = true; + //@dump-sem-ir-end +} + +// --- fail_todo_control_flow_file_scope.carbon + +library "[[@TEST_NAME]]"; + +let a: bool = true; +// CHECK:STDERR: fail_todo_control_flow_file_scope.carbon:[[@LINE+4]]:15: error: semantics TODO: `Control flow expressions are currently only supported inside functions.` [SemanticsTodo] +// CHECK:STDERR: let b: typeof(a or a) = false; +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: +let b: typeof(a or a) = false; + +// --- member_access.carbon + +library "[[@TEST_NAME]]"; + +class C { + fn G(); +} + +fn F(c: C) { + //@dump-sem-ir-begin + typeof(c).G(); + //@dump-sem-ir-end +} + +// --- generic.carbon + +library "[[@TEST_NAME]]"; + +//@dump-sem-ir-begin +fn G[T: type](x: T, unused y: typeof(x)) {} +//@dump-sem-ir-end + +fn Call() { + G(true, false); +} + +// --- fail_generic_mismatch.carbon + +library "[[@TEST_NAME]]"; + +class C {} + +fn G[T: type](x: T, unused y: typeof(x)) {} + +fn Call() { + // CHECK:STDERR: fail_generic_mismatch.carbon:[[@LINE+7]]:3: error: inconsistent deductions for value of generic parameter `T` [DeductionInconsistent] + // CHECK:STDERR: G(true, {} as C); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_generic_mismatch.carbon:[[@LINE-6]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] + // CHECK:STDERR: fn G[T: type](x: T, unused y: typeof(x)) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + G(true, {} as C); +} + +// --- fail_undeclared.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_undeclared.carbon:[[@LINE+4]]:15: error: name `undeclared` not found [NameNotFound] +// CHECK:STDERR: var x: typeof(undeclared); +// CHECK:STDERR: ^~~~~~~~~~ +// CHECK:STDERR: +var x: typeof(undeclared); + +// CHECK:STDOUT: --- basic.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %pattern_type: type = pattern_type %C [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %C.val: %C = struct_value () [concrete] +// CHECK:STDOUT: %d.patt: %pattern_type = ref_binding_pattern d [concrete] +// CHECK:STDOUT: %d.var_patt: %pattern_type = var_pattern %d.patt [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %d.var: ref %C = var_storage %d.var_patt [concrete] +// CHECK:STDOUT: %.loc9: type = type_of %c.ref in [concrete = constants.%C] { +// CHECK:STDOUT: %c.ref: ref %C = name_ref c, %c [concrete = %c.var] +// CHECK:STDOUT: } +// CHECK:STDOUT: %d: ref %C = wrapper_binding d, %d.var [concrete = %d.var] +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %d.patt: %pattern_type = ref_binding_pattern d [concrete = constants.%d.patt] +// CHECK:STDOUT: %d.var_patt: %pattern_type = var_pattern %d.patt [concrete = constants.%d.var_patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: +// CHECK:STDOUT: %.loc9_21.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc9_21.2: init %C to file.%d.var = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc9_1: init %C = converted %.loc9_21.1, %.loc9_21.2 [concrete = constants.%C.val] +// CHECK:STDOUT: assign file.%d.var, %.loc9_1 +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- type_of_type.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %pattern_type: type = pattern_type type [concrete] +// CHECK:STDOUT: %T.patt: %pattern_type = value_binding_pattern T [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %.loc7: type = type_literal type [concrete = type] +// CHECK:STDOUT: %T: type = wrapper_binding T, @__global_init.%.loc7 +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %T.patt: %pattern_type = value_binding_pattern T [concrete = constants.%T.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @__global_init() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc7: type = type_of %C.ref in [concrete = type] { +// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- not_evaluated.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %pattern_type.98b: type = pattern_type %C [concrete] +// CHECK:STDOUT: %Make.type: type = fn_type @Make [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %Make: %Make.type = struct_value () [concrete] +// CHECK:STDOUT: %c.patt: %pattern_type.98b = ref_binding_pattern c [concrete] +// CHECK:STDOUT: %c.var_patt: %pattern_type.98b = var_pattern %c.patt [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %C.val: %C = struct_value () [concrete] +// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete] +// CHECK:STDOUT: %self.param_patt.52f: %pattern_type.a96 = ref_param_pattern [concrete] +// CHECK:STDOUT: %self.patt.4b1: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt.52f [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_3.1 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc10_3.1 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete] +// CHECK:STDOUT: %self.param_patt.99a: %pattern_type.98b = ref_param_pattern [concrete] +// CHECK:STDOUT: %self.patt.cbd: %pattern_type.98b = wrapper_binding_pattern self, %self.param_patt.99a [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_3.2 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc10_3.2 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2: type = fn_type @Destroy.WithSelf.SelfDestruct.loc10_3.2 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.2: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2 = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generated { +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] { +// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f] +// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0 +// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc10_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] { +// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f] +// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0 +// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] { +// CHECK:STDOUT: %self.param_patt: %pattern_type.98b = ref_param_pattern [concrete = constants.%self.param_patt.99a] +// CHECK:STDOUT: %self.patt: %pattern_type.98b = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.cbd] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %self.param: ref %C = ref_param call_param0 +// CHECK:STDOUT: %self: ref %C = wrapper_binding self, %self.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc10_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] { +// CHECK:STDOUT: %self.param_patt: %pattern_type.98b = ref_param_pattern [concrete = constants.%self.param_patt.99a] +// CHECK:STDOUT: %self.patt: %pattern_type.98b = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.cbd] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %self.param: ref %C = ref_param call_param0 +// CHECK:STDOUT: %self: ref %C = wrapper_binding self, %self.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %c.var: ref %C = var_storage %c.var_patt +// CHECK:STDOUT: %.loc10_35.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc10_35.2: init %C to %c.var = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc10_3: init %C = converted %.loc10_35.1, %.loc10_35.2 [concrete = constants.%C.val] +// CHECK:STDOUT: assign %c.var, %.loc10_3 +// CHECK:STDOUT: %.loc10_30: type = type_of %Make.call in [concrete = constants.%C] { +// CHECK:STDOUT: %Make.ref: %Make.type = name_ref Make, file.%Make.decl [concrete = constants.%Make] +// CHECK:STDOUT: %.loc10_29: ref %C = temporary_storage +// CHECK:STDOUT: %Make.call: init %C to %.loc10_29 = call %Make.ref() +// CHECK:STDOUT: } +// CHECK:STDOUT: %c: ref %C = wrapper_binding c, %c.var +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %c.patt: %pattern_type.98b = ref_binding_pattern c [concrete = constants.%c.patt] +// CHECK:STDOUT: %c.var_patt: %pattern_type.98b = var_pattern %c.patt [concrete = constants.%c.var_patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: = bound_method %c.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2 +// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%c.var) +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_3.1(%self.param: ref %empty_struct_type) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.1(%self.param: ref %empty_struct_type) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_3.1(%self.param: ref %empty_struct_type) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param) +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_3.2(%self.param: ref %C) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.2(%self.param: ref %C) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_3.2(%self.param: ref %C) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param) +// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- control_flow.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %pattern_type: type = pattern_type bool [concrete] +// CHECK:STDOUT: %false: bool = bool_literal false [concrete] +// CHECK:STDOUT: %c.patt: %pattern_type = value_binding_pattern c [concrete] +// CHECK:STDOUT: %true: bool = bool_literal true [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%a.param: bool, %b.param: bool) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %true: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: %.loc6_31: type = type_of %.loc6_26 in [concrete = bool] { +// CHECK:STDOUT: %a.ref: bool = name_ref a, %a +// CHECK:STDOUT: %false: bool = bool_literal false [concrete = constants.%false] +// CHECK:STDOUT: if %a.ref br !and.rhs else br !.loc6(%false) +// CHECK:STDOUT: +// CHECK:STDOUT: !and.rhs: +// CHECK:STDOUT: %b.ref: bool = name_ref b, %b +// CHECK:STDOUT: br !.loc6(%b.ref) +// CHECK:STDOUT: +// CHECK:STDOUT: !.loc6: +// CHECK:STDOUT: %.loc6_26: bool = block_arg !.loc6 +// CHECK:STDOUT: } +// CHECK:STDOUT: %c: bool = wrapper_binding c, %true +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %c.patt: %pattern_type = value_binding_pattern c [concrete = constants.%c.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- member_access.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %C.G.type: type = fn_type @C.G [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %C.G: %C.G.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%c.param: %C) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc10: type = type_of %c.ref in [concrete = constants.%C] { +// CHECK:STDOUT: %c.ref: %C = name_ref c, %c +// CHECK:STDOUT: } +// CHECK:STDOUT: %G.ref: %C.G.type = name_ref G, @C.%C.G.decl [concrete = constants.%C.G] +// CHECK:STDOUT: %C.G.call: init %empty_tuple.type = call %G.ref() +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- generic.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %.Self.frozen: type = symbolic_binding .Self [symbolic_self] +// CHECK:STDOUT: %pattern_type.9a5: type = pattern_type type [concrete] +// CHECK:STDOUT: %T.patt: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic] +// CHECK:STDOUT: %T: type = symbolic_binding T, 0 [symbolic] +// CHECK:STDOUT: %pattern_type.51d: type = pattern_type %T [symbolic] +// CHECK:STDOUT: %x.param_patt.91d: %pattern_type.51d = value_param_pattern [symbolic] +// CHECK:STDOUT: %x.patt.260: %pattern_type.51d = wrapper_binding_pattern x, %x.param_patt.91d [symbolic] +// CHECK:STDOUT: %y.param_patt.be3: %pattern_type.51d = value_param_pattern [symbolic] +// CHECK:STDOUT: %y.patt.678: %pattern_type.51d = wrapper_binding_pattern y, %y.param_patt.be3 [symbolic] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %require_complete: = require_complete_type %T [symbolic] +// CHECK:STDOUT: %pattern_type.831: type = pattern_type bool [concrete] +// CHECK:STDOUT: %x.param_patt.37d: %pattern_type.831 = value_param_pattern [concrete] +// CHECK:STDOUT: %x.patt.e47: %pattern_type.831 = wrapper_binding_pattern x, %x.param_patt.37d [concrete] +// CHECK:STDOUT: %y.param_patt.077: %pattern_type.831 = value_param_pattern [concrete] +// CHECK:STDOUT: %y.patt.252: %pattern_type.831 = wrapper_binding_pattern y, %y.param_patt.077 [concrete] +// CHECK:STDOUT: %complete_type: = complete_type_witness bool [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] { +// CHECK:STDOUT: %T.patt.loc5_7.1: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc5_7.2 (constants.%T.patt)] +// CHECK:STDOUT: %x.param_patt.loc5_16.1: @G.%pattern_type (%pattern_type.51d) = value_param_pattern [symbolic = %x.param_patt.loc5_16.2 (constants.%x.param_patt.91d)] +// CHECK:STDOUT: %x.patt.loc5_16.1: @G.%pattern_type (%pattern_type.51d) = wrapper_binding_pattern x, %x.param_patt.loc5_16.1 [symbolic = %x.patt.loc5_16.2 (constants.%x.patt.260)] +// CHECK:STDOUT: %y.param_patt.loc5_29.1: @G.%pattern_type (%pattern_type.51d) = value_param_pattern [symbolic = %y.param_patt.loc5_29.2 (constants.%y.param_patt.be3)] +// CHECK:STDOUT: %y.patt.loc5_29.1: @G.%pattern_type (%pattern_type.51d) = wrapper_binding_pattern y, %y.param_patt.loc5_29.1 [symbolic = %y.patt.loc5_29.2 (constants.%y.patt.678)] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %.loc5_9.1: type = splice_block %.loc5_9.2 [concrete = type] { +// CHECK:STDOUT: %.Self.frozen: type = symbolic_binding .Self [symbolic_self = constants.%.Self.frozen] +// CHECK:STDOUT: %.loc5_9.2: type = type_literal type [concrete = type] +// CHECK:STDOUT: } +// CHECK:STDOUT: %T.loc5_7.2: type = symbolic_binding T, 0 [symbolic = %T.loc5_7.1 (constants.%T)] +// CHECK:STDOUT: %x.param: @G.%T.loc5_7.1 (%T) = value_param call_param0 +// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc5_7.2 [symbolic = %T.loc5_7.1 (constants.%T)] +// CHECK:STDOUT: %x: @G.%T.loc5_7.1 (%T) = wrapper_binding x, %x.param +// CHECK:STDOUT: %y.param: @G.%T.loc5_7.1 (%T) = value_param call_param1 +// CHECK:STDOUT: %.loc5_39: type = type_of %x.ref in [symbolic = %T.loc5_7.1 (constants.%T)] { +// CHECK:STDOUT: %x.ref: @G.%T.loc5_7.1 (%T) = name_ref x, %x +// CHECK:STDOUT: } +// CHECK:STDOUT: %y: @G.%T.loc5_7.1 (%T) = wrapper_binding y, %y.param +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: generic fn @G(%T.loc5_7.2: type) { +// CHECK:STDOUT: %T.patt.loc5_7.2: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc5_7.2 (constants.%T.patt)] +// CHECK:STDOUT: %T.loc5_7.1: type = symbolic_binding T, 0 [symbolic = %T.loc5_7.1 (constants.%T)] +// CHECK:STDOUT: %pattern_type: type = pattern_type %T.loc5_7.1 [symbolic = %pattern_type (constants.%pattern_type.51d)] +// CHECK:STDOUT: %x.param_patt.loc5_16.2: @G.%pattern_type (%pattern_type.51d) = value_param_pattern [symbolic = %x.param_patt.loc5_16.2 (constants.%x.param_patt.91d)] +// CHECK:STDOUT: %x.patt.loc5_16.2: @G.%pattern_type (%pattern_type.51d) = wrapper_binding_pattern x, %x.param_patt.loc5_16.2 [symbolic = %x.patt.loc5_16.2 (constants.%x.patt.260)] +// CHECK:STDOUT: %y.param_patt.loc5_29.2: @G.%pattern_type (%pattern_type.51d) = value_param_pattern [symbolic = %y.param_patt.loc5_29.2 (constants.%y.param_patt.be3)] +// CHECK:STDOUT: %y.patt.loc5_29.2: @G.%pattern_type (%pattern_type.51d) = wrapper_binding_pattern y, %y.param_patt.loc5_29.2 [symbolic = %y.patt.loc5_29.2 (constants.%y.patt.678)] +// CHECK:STDOUT: +// CHECK:STDOUT: !definition: +// CHECK:STDOUT: %require_complete: = require_complete_type %T.loc5_7.1 [symbolic = %require_complete (constants.%require_complete)] +// CHECK:STDOUT: +// CHECK:STDOUT: fn(%x.param: @G.%T.loc5_7.1 (%T), %y.param: @G.%T.loc5_7.1 (%T)) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @G(constants.%T) { +// CHECK:STDOUT: %T.patt.loc5_7.2 => constants.%T.patt +// CHECK:STDOUT: %T.loc5_7.1 => constants.%T +// CHECK:STDOUT: %pattern_type => constants.%pattern_type.51d +// CHECK:STDOUT: %x.param_patt.loc5_16.2 => constants.%x.param_patt.91d +// CHECK:STDOUT: %x.patt.loc5_16.2 => constants.%x.patt.260 +// CHECK:STDOUT: %y.param_patt.loc5_29.2 => constants.%y.param_patt.be3 +// CHECK:STDOUT: %y.patt.loc5_29.2 => constants.%y.patt.678 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: specific @G(bool) { +// CHECK:STDOUT: %T.patt.loc5_7.2 => constants.%T.patt +// CHECK:STDOUT: %T.loc5_7.1 => bool +// CHECK:STDOUT: %pattern_type => constants.%pattern_type.831 +// CHECK:STDOUT: %x.param_patt.loc5_16.2 => constants.%x.param_patt.37d +// CHECK:STDOUT: %x.patt.loc5_16.2 => constants.%x.patt.e47 +// CHECK:STDOUT: %y.param_patt.loc5_29.2 => constants.%y.param_patt.077 +// CHECK:STDOUT: %y.patt.loc5_29.2 => constants.%y.patt.252 +// CHECK:STDOUT: +// CHECK:STDOUT: !definition: +// CHECK:STDOUT: %require_complete => constants.%complete_type +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/format/formatter.cpp b/toolchain/format/formatter.cpp index e01aa4f7f61d..965d844c32a9 100644 --- a/toolchain/format/formatter.cpp +++ b/toolchain/format/formatter.cpp @@ -97,7 +97,7 @@ auto Formatter::Run() -> bool { Lex::TokenKind::OpenSquareBracket}) && (prev_token_kind_.IsOneOf( {Lex::TokenKind::Identifier, Lex::TokenKind::Array, - Lex::TokenKind::CloseParen, + Lex::TokenKind::TypeOf, Lex::TokenKind::CloseParen, Lex::TokenKind::CloseSquareBracket}) || prev_token_kind_.is_sized_type_literal())) { PrepareForPackedContent(token_start_line); diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index 29cf3eb19843..42aac1025af3 100644 --- a/toolchain/lex/token_kind.def +++ b/toolchain/lex/token_kind.def @@ -227,6 +227,7 @@ CARBON_KEYWORD_TOKEN(Template, "template") CARBON_KEYWORD_TOKEN(Then, "then") CARBON_KEYWORD_TOKEN(True, "true") CARBON_KEYWORD_TOKEN(Type, "type") +CARBON_KEYWORD_TOKEN(TypeOf, "typeof") // Underscore is tokenized as a keyword because it's part of identifiers. CARBON_KEYWORD_TOKEN(Underscore, "_") CARBON_KEYWORD_TOKEN(Unsafe, "unsafe") diff --git a/toolchain/lower/testdata/typeof/not_evaluated.carbon b/toolchain/lower/testdata/typeof/not_evaluated.carbon new file mode 100644 index 000000000000..e454ca278e59 --- /dev/null +++ b/toolchain/lower/testdata/typeof/not_evaluated.carbon @@ -0,0 +1,59 @@ +// 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/bool.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/typeof/not_evaluated.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/typeof/not_evaluated.carbon + +// The operand of `typeof` should not be evaluated at runtime. + +class X { + var a: bool; +} + +fn Get() -> X; +fn Make(x: X) -> bool; + +fn F(a: bool, b: bool) -> bool { + let c: typeof(Make(Get())) = a; + let unused d: typeof(a and b) = c; + return c; +} + +// CHECK:STDOUT: ; --- +// CHECK:STDOUT: ; ModuleID = 'not_evaluated.carbon' +// CHECK:STDOUT: source_filename = "not_evaluated.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CGet.Main(ptr sret({ i1 })) +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @_CMake.Main(ptr) +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i1 @_CF.Main(i1 %a, i1 %b) #0 !dbg !25 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret i1 %a, !dbg !39 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nounwind } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.dbg.cu = !{!17} +// CHECK:STDOUT: !llvm.module.flags = !{!2, !3} +// CHECK:STDOUT: +// CHECK:STDOUT: !2 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !3 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !16 = !DIFile(filename: "not_evaluated.carbon", directory: "") +// CHECK:STDOUT: !17 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !16, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !18 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +// CHECK:STDOUT: !23 = !{!18, !18, !18} +// CHECK:STDOUT: !24 = !DISubroutineType(types: !23) +// CHECK:STDOUT: !25 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !16, line: 22, type: !24, spFlags: DISPFlagDefinition, unit: !17, retainedNodes: !40) +// CHECK:STDOUT: !26 = !DILocalVariable(arg: 1, scope: !25, type: !18) +// CHECK:STDOUT: !27 = !DILocalVariable(arg: 2, scope: !25, type: !18) +// CHECK:STDOUT: !39 = !DILocation(line: 25, column: 3, scope: !25) +// CHECK:STDOUT: !40 = !{!26, !27} +// CHECK:STDOUT: diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index 2f539069c258..2542de0b6cc8 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/toolchain/parse/handle_expr.cpp @@ -179,6 +179,11 @@ auto HandleExprInPostfix(Context& context) -> void { context.PushState(StateKind::FormLiteral); break; } + case Lex::TokenKind::TypeOf: { + context.PushState(state); + context.PushState(StateKind::TypeOfExpr); + break; + } case Lex::TokenKind::Package: { context.AddLeafNode(NodeKind::PackageExpr, context.Consume()); if (context.PositionKind() != Lex::TokenKind::Period) { diff --git a/toolchain/parse/handle_typeof_expr.cpp b/toolchain/parse/handle_typeof_expr.cpp new file mode 100644 index 000000000000..443dcc3490e5 --- /dev/null +++ b/toolchain/parse/handle_typeof_expr.cpp @@ -0,0 +1,34 @@ +// 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 "toolchain/lex/token_kind.h" +#include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/context.h" +#include "toolchain/parse/handle.h" +#include "toolchain/parse/node_kind.h" +#include "toolchain/parse/state.h" + +namespace Carbon::Parse { + +auto HandleTypeOfExpr(Context& context) -> void { + auto state = context.PopState(); + auto keyword = context.ConsumeChecked(Lex::TokenKind::TypeOf); + context.AddLeafNode(NodeKind::TypeOfExprKeyword, keyword); + if (auto open_paren = context.ConsumeAndAddOpenParen( + keyword, NodeKind::TypeOfExprOpenParen)) { + // Stash the open paren token for use by ConsumeAndAddCloseSymbol. + state.token = *open_paren; + } else { + state.has_error = true; + } + context.PushState(state, StateKind::TypeOfExprFinish); + context.PushState(StateKind::Expr); +} + +auto HandleTypeOfExprFinish(Context& context) -> void { + auto state = context.PopState(); + context.ConsumeAndAddCloseSymbol(state, NodeKind::TypeOfExpr); +} + +} // namespace Carbon::Parse diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index ab645b46b918..6b982d6042f8 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -296,6 +296,10 @@ CARBON_PARSE_NODE_KIND_EXPRESSION(FormLiteralKeyword) CARBON_PARSE_NODE_KIND_EXPRESSION(FormLiteralOpenParen) CARBON_PARSE_NODE_KIND_EXPRESSION(FormLiteral) +CARBON_PARSE_NODE_KIND_EXPRESSION(TypeOfExprKeyword) +CARBON_PARSE_NODE_KIND_EXPRESSION(TypeOfExprOpenParen) +CARBON_PARSE_NODE_KIND_EXPRESSION(TypeOfExpr) + CARBON_PARSE_NODE_KIND_EXPRESSION(ParenExprStart) CARBON_PARSE_NODE_KIND_EXPRESSION(ParenExpr) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 444f74f5b376..286cf58c9157 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -144,6 +144,21 @@ CARBON_PARSE_STATE(PrimitiveFormFinish) // (state done) CARBON_PARSE_STATE(FormLiteralFinish) +// Handles the `typeof` keyword and opening paren in a `typeof` expression. +// +// typeof(x) +// ^~~~~~~ +// 1. Expr +// 2. TypeOfExprFinish +CARBON_PARSE_STATE(TypeOfExpr) + +// Handles finishing the `typeof` expression. +// +// typeof(x) +// ^ +// (state done) +CARBON_PARSE_STATE(TypeOfExprFinish) + // Handles the `{` of a brace expression. // // {} diff --git a/toolchain/parse/testdata/typeof/basic.carbon b/toolchain/parse/testdata/typeof/basic.carbon new file mode 100644 index 000000000000..6474dad3db3e --- /dev/null +++ b/toolchain/parse/testdata/typeof/basic.carbon @@ -0,0 +1,181 @@ +// 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/typeof/basic.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/typeof/basic.carbon + +// --- basic.carbon + +var x: typeof(y); + +// --- member_access.carbon + +fn F() { + typeof(x).G(); +} + +// --- fail_no_paren.carbon + +// CHECK:STDERR: fail_no_paren.carbon:[[@LINE+4]]:15: error: expected `(` after `typeof` [ExpectedParenAfter] +// CHECK:STDERR: var x: typeof y; +// CHECK:STDERR: ^ +// CHECK:STDERR: +var x: typeof y; + +// --- fail_no_expr.carbon + +// CHECK:STDERR: fail_no_expr.carbon:[[@LINE+4]]:15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: var x: typeof(); +// CHECK:STDERR: ^ +// CHECK:STDERR: +var x: typeof(); + +// --- fail_no_close_paren.carbon + +// CHECK:STDERR: fail_no_close_paren.carbon:[[@LINE+4]]:17: error: unexpected tokens before `)` [ExpectedCloseSymbol] +// CHECK:STDERR: var x: typeof(y z); +// CHECK:STDERR: ^ +// CHECK:STDERR: +var x: typeof(y z); + +// --- fail_no_close_paren_before_eof_at_eof.carbon + +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+11]]:14: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] +// CHECK:STDERR: var x: typeof( +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+8]]:15: note: possibly missing `)` here [PossiblyMissingBracketHere] +// CHECK:STDERR: var x: typeof( +// CHECK:STDERR: ^ +// CHECK:STDERR: +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+4]]:15: error: expected expression [ExpectedExpr] +// CHECK:STDERR: var x: typeof( +// CHECK:STDERR: ^ +// CHECK:STDERR: +var x: typeof( +// CHECK:STDERR: fail_no_close_paren_before_eof_at_eof.carbon:[[@LINE+4]]:1: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: +// CHECK:STDERR: ^ +// CHECK:STDERR: + +// --- fail_no_close_paren_after_expr_at_eof.carbon + +// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+7]]:14: error: opening symbol without a corresponding closing symbol [UnmatchedOpening] +// CHECK:STDERR: var x: typeof(y +// CHECK:STDERR: ^ +// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+4]]:16: note: possibly missing `)` here [PossiblyMissingBracketHere] +// CHECK:STDERR: var x: typeof(y +// CHECK:STDERR: ^ +// CHECK:STDERR: +var x: typeof(y + +// CHECK:STDERR: fail_no_close_paren_after_expr_at_eof.carbon:[[@LINE+106]]:26: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: // CHECK:STDOUT: (root) +// CHECK:STDERR: ^ +// CHECK:STDERR: +// CHECK:STDOUT: - filename: basic.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ │ ├─IdentifierNameExpr 'y' +// CHECK:STDOUT: │ │ ├─TypeOfExpr ')' +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: member_access.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'F' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ ╭─FunctionDefinitionStart '{' +// CHECK:STDOUT: │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'x' +// CHECK:STDOUT: │ │ ╭─TypeOfExpr ')' +// CHECK:STDOUT: │ │ ├─IdentifierNameNotBeforeSignature 'G' +// CHECK:STDOUT: │ │ ╭─MemberAccessExpr '.' +// CHECK:STDOUT: │ │ ╭─CallExprStart '(' +// CHECK:STDOUT: │ │ ╭─CallExpr ')' +// CHECK:STDOUT: │ ├─ExprStatement ';' +// CHECK:STDOUT: ├─FunctionDefinition '}' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_no_paren.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen 'typeof' has_error +// CHECK:STDOUT: │ │ │ ├─IdentifierNameExpr 'y' +// CHECK:STDOUT: │ │ ├─TypeOfExpr 'typeof' has_error +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_no_expr.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ │ ├─InvalidParse ')' has_error +// CHECK:STDOUT: │ │ ├─TypeOfExpr ')' has_error +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_no_close_paren.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ │ ├─IdentifierNameExpr 'y' +// CHECK:STDOUT: │ │ ├─TypeOfExpr ')' has_error +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_no_close_paren_before_eof_at_eof.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ │ ├─InvalidParse ')' has_error +// CHECK:STDOUT: │ │ ├─TypeOfExpr ')' has_error +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ')' has_error +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_no_close_paren_after_expr_at_eof.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─VariableIntroducer 'var' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'x' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ │ ╭─TypeOfExprKeyword 'typeof' +// CHECK:STDOUT: │ │ │ ├─TypeOfExprOpenParen '(' +// CHECK:STDOUT: │ │ │ ├─IdentifierNameExpr 'y' +// CHECK:STDOUT: │ │ ├─TypeOfExpr ')' +// CHECK:STDOUT: │ │ ╭─VarBindingPattern ':' +// CHECK:STDOUT: │ ├─VariablePattern 'var' +// CHECK:STDOUT: ├─VariableDecl ')' has_error +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 39c655367802..7481f442d682 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -1111,6 +1111,25 @@ struct FormLiteral { Lex::CloseParenTokenIndex token; }; +using TypeOfExprKeyword = + LeafNode; + +using TypeOfExprOpenParen = + LeafNode; + +// A `typeof` expression: `typeof(expr)`. +struct TypeOfExpr { + static constexpr auto Kind = + NodeKind::TypeOfExpr.Define({.category = NodeCategory::Expr, + .bracketed_by = NodeKind::TypeOfExprKeyword, + .child_count = 3}); + + TypeOfExprKeywordId keyword; + TypeOfExprOpenParenId start; + AnyExprId operand; + Lex::CloseParenTokenIndex token; +}; + // The opening portion of an indexing expression: `a[`. // // TODO: Consider flattening this into `IndexExpr`. diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index f41986f5affc..15bdbb546111 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -172,6 +172,7 @@ CARBON_SEM_IR_INST_KIND(TupleType) CARBON_SEM_IR_INST_KIND(TupleValue) CARBON_SEM_IR_INST_KIND(TypeComponentOf) CARBON_SEM_IR_INST_KIND(TypeLiteral) +CARBON_SEM_IR_INST_KIND(TypeOf) CARBON_SEM_IR_INST_KIND(TypeOfInst) CARBON_SEM_IR_INST_KIND(UnaryOperatorNot) CARBON_SEM_IR_INST_KIND(UnboundElementType) diff --git a/toolchain/sem_ir/inst_namer.cpp b/toolchain/sem_ir/inst_namer.cpp index 3d2ce1a58561..2b409e89d07d 100644 --- a/toolchain/sem_ir/inst_namer.cpp +++ b/toolchain/sem_ir/inst_namer.cpp @@ -1501,6 +1501,14 @@ auto InstNamer::NamingContext::NameInst() -> void { AddInstName(""); return; } + case CARBON_KIND(TypeOf inst): { + for (auto block_id : + sem_ir().expr_regions().Get(inst.operand_region_id).block_ids) { + PushBlockId(scope_id_, block_id); + } + AddInstName(""); + return; + } case CARBON_KIND(UnboundElementType inst): { if (auto class_ty = sem_ir().insts().TryGetAs(inst.class_type_inst_id)) { diff --git a/toolchain/sem_ir/stringify.cpp b/toolchain/sem_ir/stringify.cpp index f0035beed8cb..85cde308cb3e 100644 --- a/toolchain/sem_ir/stringify.cpp +++ b/toolchain/sem_ir/stringify.cpp @@ -355,10 +355,12 @@ class Stringifier { template auto StringifyInst(InstId inst_id, InstT inst) -> void { // This doesn't use requires so that more specific overloads are chosen when - // provided. - static_assert(InstT::Kind.is_type() != InstIsType::Always || - std::same_as, - "Types should have a dedicated overload"); + // provided. Indirect constants can be printed by desugaring. + static_assert( + InstT::Kind.is_type() != InstIsType::Always || + InstT::Kind.constant_kind() == InstConstantKind::Indirect || + std::same_as, + "Types should have a dedicated overload"); // TODO: We should have Stringify support for all types where // InstT::Kind.constant_kind() is neither Never nor Indirect. StringifyInstDefault(inst_id, inst); diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index a493978a0859..9b79b1b8972c 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -2313,6 +2313,22 @@ struct TypeLiteral { TypeInstId value_id; }; +// A `typeof(expr)` expression. The operand is held in a separate expression +// region, which is never evaluated at runtime; only its type is used. The +// constant value of this instruction is the type of the operand. +struct TypeOf { + static constexpr auto Kind = InstKind::TypeOf.Define( + {.ir_name = "type_of", + .expr_category = ExprCategory::Value, + .is_type = InstIsType::Always}); + + // Always the builtin type TypeType. + TypeId type_id; + // The region that computes the operand expression. The operand is the + // region's `result_id`. + ExprRegionId operand_region_id; +}; + // Returns the type of the instruction produced by an action. For example, given // // %inst: = some_action