From 702d0d8a536a59c81169157bc83d40128eb1a9c9 Mon Sep 17 00:00:00 2001 From: josh11b <15258583+josh11b@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:26:23 -0700 Subject: [PATCH] Parsing of designators like `.x` or `.Self` (#4254) These appear in `where` clauses, as in: ``` U:! InterfaceB where .C = Vector(.D) V:! type where Vector(.Self) impls Sortable ``` `.Self` can additionally appear in type expressions in a binding pattern such as `T:! InterfaceA(.Self)`. --------- Co-authored-by: Josh L Co-authored-by: Jon Ross-Perkins --- toolchain/check/handle_where.cpp | 20 +++ .../no_prelude/fail_designator.carbon | 127 ++++++++++++++ toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/parse/handle_expr.cpp | 38 +++++ toolchain/parse/node_kind.def | 3 + .../testdata/where_expr/designators.carbon | 155 ++++++++++++++++++ toolchain/parse/typed_nodes.h | 16 ++ 7 files changed, 360 insertions(+) create mode 100644 toolchain/check/handle_where.cpp create mode 100644 toolchain/check/testdata/where_expr/no_prelude/fail_designator.carbon create mode 100644 toolchain/parse/testdata/where_expr/designators.carbon diff --git a/toolchain/check/handle_where.cpp b/toolchain/check/handle_where.cpp new file mode 100644 index 000000000000..8aa19ba57ff9 --- /dev/null +++ b/toolchain/check/handle_where.cpp @@ -0,0 +1,20 @@ +// 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/convert.h" +#include "toolchain/check/handle.h" + +namespace Carbon::Check { + +auto HandleParseNode(Context& context, Parse::SelfTypeNameId node_id) -> bool { + return context.TODO(node_id, "HandleSelfTypeName"); +} + +auto HandleParseNode(Context& context, Parse::DesignatorExprId node_id) + -> bool { + return context.TODO(node_id, "HandleDesignatorExpr"); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/testdata/where_expr/no_prelude/fail_designator.carbon b/toolchain/check/testdata/where_expr/no_prelude/fail_designator.carbon new file mode 100644 index 000000000000..1e3bd174186f --- /dev/null +++ b/toolchain/check/testdata/where_expr/no_prelude/fail_designator.carbon @@ -0,0 +1,127 @@ +// 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/check/testdata/where_expr/no_prelude/fail_designator.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/where_expr/no_prelude/fail_designator.carbon + +// --- fail_designator_matches_var.carbon + +library "designator_matches_var"; + +fn Foo() -> () { + var x: (); + // CHECK:STDERR: fail_designator_matches_var.carbon:[[@LINE+4]]:10: ERROR: Semantics TODO: `HandleDesignatorExpr`. + // CHECK:STDERR: return .x; + // CHECK:STDERR: ^~ + // CHECK:STDERR: + return .x; +} + +// --- fail_unknown_designator.carbon + +library "unknown_designator"; + +fn Bar() -> () { + // CHECK:STDERR: fail_unknown_designator.carbon:[[@LINE+4]]:10: ERROR: Semantics TODO: `HandleDesignatorExpr`. + // CHECK:STDERR: return .undef; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + return .undef; +} + +// --- fail_dot_self_method_return_value.carbon + +library "dot_self_method_return_value"; + +class C { + // CHECK:STDERR: fail_dot_self_method_return_value.carbon:[[@LINE+4]]:28: ERROR: Semantics TODO: `HandleSelfTypeName`. + // CHECK:STDERR: fn F() -> Self { return .Self; } + // CHECK:STDERR: ^~~~ + // CHECK:STDERR: + fn F() -> Self { return .Self; } +} + +// --- fail_dot_self_method_return_type.carbon + +library "dot_self_method_return_type"; + +class D { + // CHECK:STDERR: fail_dot_self_method_return_type.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `HandleSelfTypeName`. + // CHECK:STDERR: fn G() -> .Self { return Self; } + // CHECK:STDERR: ^~~~ + fn G() -> .Self { return Self; } +} + +// CHECK:STDOUT: --- fail_designator_matches_var.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %Foo.type: type = fn_type @Foo [template] +// CHECK:STDOUT: %Foo: %Foo.type = struct_value () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file {} +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Foo() -> %.1 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_unknown_designator.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %Bar.type: type = fn_type @Bar [template] +// CHECK:STDOUT: %Bar: %Bar.type = struct_value () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file {} +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Bar() -> %.1 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_dot_self_method_return_value.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %C: type = class_type @C [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %.2: type = struct_type {} [template] +// CHECK:STDOUT: %.3: type = ptr_type %.2 [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file {} +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] { +// CHECK:STDOUT: %Self.ref: type = name_ref Self, constants.%C [template = constants.%C] +// CHECK:STDOUT: %return.var: ref %C = var +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%C +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> @C.%return.var: %C { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_dot_self_method_return_type.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %D: type = class_type @D [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file {} +// CHECK:STDOUT: +// CHECK:STDOUT: class @D { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%D +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 82bc815bc7a3..964dce8827f5 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -65,6 +65,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedCloseSymbol) CARBON_DIAGNOSTIC_KIND(ExpectedCodeBlock) CARBON_DIAGNOSTIC_KIND(ExpectedExpr) CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPeriodOrArrow) +CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierOrSelfAfterPeriod) CARBON_DIAGNOSTIC_KIND(ExpectedBindingPattern) CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter) CARBON_DIAGNOSTIC_KIND(ExpectedExprSemi) diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index 7c69771a0e5e..56ad2a5ffb82 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/toolchain/parse/handle_expr.cpp @@ -169,6 +169,44 @@ auto HandleExprInPostfix(Context& context) -> void { context.PushState(state); break; } + case Lex::TokenKind::Period: { + // For periods, we look at the next token to form a designator like + // `.Member` or `.Self`. + auto period = context.Consume(); + if (context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier, + NodeKind::IdentifierName)) { + // OK, `.` identifier. + } else if (context.ConsumeAndAddLeafNodeIf( + Lex::TokenKind::SelfTypeIdentifier, + NodeKind::SelfTypeName)) { + // OK, `.Self`. + } else { + CARBON_DIAGNOSTIC(ExpectedIdentifierOrSelfAfterPeriod, Error, + "Expected identifier or `Self` after `.`."); + context.emitter().Emit(*context.position(), + ExpectedIdentifierOrSelfAfterPeriod); + // Only consume if it is a number or word. + if (context.PositionKind().is_keyword()) { + context.AddLeafNode(NodeKind::IdentifierName, context.Consume(), + /*has_error=*/true); + } else if (context.PositionIs(Lex::TokenKind::IntLiteral)) { + context.AddLeafNode(NodeKind::InvalidParse, context.Consume(), + /*has_error=*/true); + } else { + context.AddLeafNode(NodeKind::InvalidParse, *context.position(), + /*has_error=*/true); + // Indicate the error to the parent state so that it can avoid + // producing more errors. We only do this on this path where we don't + // consume the token after the period, where we expect further errors + // since we likely haven't recovered. + context.ReturnErrorOnState(); + } + state.has_error = true; + } + context.AddNode(NodeKind::DesignatorExpr, period, state.has_error); + context.PushState(state); + break; + } default: { // Add a node to keep the parse tree balanced. context.AddLeafNode(NodeKind::InvalidParse, *context.position(), diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 955284aa9189..e310312cd470 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -274,6 +274,9 @@ CARBON_PARSE_NODE_KIND(ShortCircuitOperandOr) CARBON_PARSE_NODE_KIND(ShortCircuitOperatorAnd) CARBON_PARSE_NODE_KIND(ShortCircuitOperatorOr) +CARBON_PARSE_NODE_KIND(SelfTypeName) +CARBON_PARSE_NODE_KIND(DesignatorExpr) + CARBON_PARSE_NODE_KIND(IfExprIf) CARBON_PARSE_NODE_KIND(IfExprThen) CARBON_PARSE_NODE_KIND(IfExprElse) diff --git a/toolchain/parse/testdata/where_expr/designators.carbon b/toolchain/parse/testdata/where_expr/designators.carbon new file mode 100644 index 000000000000..9095bcb4112a --- /dev/null +++ b/toolchain/parse/testdata/where_expr/designators.carbon @@ -0,0 +1,155 @@ +// 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/where_expr/designators.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/where_expr/designators.carbon + +// --- designator.carbon + +fn Success() { + .x; + .Self; +} + +// --- fail_numeric_designator.carbon + +fn FailNumeric() { + // CHECK:STDERR: fail_numeric_designator.carbon:[[@LINE+4]]:4: ERROR: Expected identifier or `Self` after `.`. + // CHECK:STDERR: .1; + // CHECK:STDERR: ^ + // CHECK:STDERR: + .1; +} + +// --- fail_keyword_designator.carbon + +fn FailKeyword() { + // CHECK:STDERR: fail_keyword_designator.carbon:[[@LINE+4]]:4: ERROR: Expected identifier or `Self` after `.`. + // CHECK:STDERR: .base; + // CHECK:STDERR: ^~~~ + // CHECK:STDERR: + .base; +} + +// --- fail_empty_designator.carbon + +fn FailEmpty() { + // CHECK:STDERR: fail_empty_designator.carbon:[[@LINE+4]]:4: ERROR: Expected identifier or `Self` after `.`. + // CHECK:STDERR: .; + // CHECK:STDERR: ^ + // CHECK:STDERR: + .; +} + +// --- fail_star_designator.carbon + +fn FailStar() { + // CHECK:STDERR: fail_star_designator.carbon:[[@LINE+4]]:4: ERROR: Expected identifier or `Self` after `.`. + // CHECK:STDERR: .*; + // CHECK:STDERR: ^ + // CHECK:STDERR: + .*; +} + +// --- fail_designator_eof.carbon + +var x: i32 = . + +// CHECK:STDERR: fail_designator_eof.carbon:[[@LINE+93]]:21: ERROR: Expected identifier or `Self` after `.`. +// CHECK:STDERR: // CHECK:STDOUT: ] +// CHECK:STDERR: ^ +// CHECK:STDERR: +// CHECK:STDERR: fail_designator_eof.carbon:[[@LINE+89]]:21: ERROR: `var` declarations must end with a `;`. +// CHECK:STDERR: // CHECK:STDOUT: ] +// CHECK:STDERR: ^ +// CHECK:STDOUT: - filename: designator.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'Success'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'SelfTypeName', text: 'Self'}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_numeric_designator.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'FailNumeric'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '1', has_error: yes}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_keyword_designator.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'FailKeyword'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'base', has_error: yes}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_empty_designator.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'FailEmpty'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_star_designator.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'FailStar'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'TuplePattern', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '*', has_error: yes}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'PostfixOperatorStar', text: '*', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ExprStatement', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_designator_eof.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'IdentifierName', text: 'x'}, +// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, +// CHECK:STDOUT: {kind: 'BindingPattern', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '', has_error: yes}, +// CHECK:STDOUT: {kind: 'DesignatorExpr', text: '.', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: '.', has_error: yes, subtree_size: 8}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index ddf0da98d2a9..cab0eca359df 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -1016,6 +1016,22 @@ struct IfExprElse { AnyExprId else_result; }; +// The `Self` in a context where it is treated as a name rather than an +// expression, such as `.Self`. +using SelfTypeName = + LeafNode; + +// `.Member` or `.Self` in an expression context, used in `where` and `require` +// clauses. +// TODO: Do we want to support `.1`, a designator for accessing a tuple member? +struct DesignatorExpr { + static constexpr auto Kind = NodeKind::DesignatorExpr.Define( + {.category = NodeCategory::Expr, .child_count = 1}); + + Lex::PeriodTokenIndex token; + NodeIdOneOf name; +}; + // Choice nodes // ------------