Sfoglia il codice sorgente

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 <josh11b@users.noreply.github.com>
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
josh11b 1 anno fa
parent
commit
702d0d8a53

+ 20 - 0
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

+ 127 - 0
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 <return slot>
+// 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:

+ 1 - 0
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)

+ 38 - 0
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(),

+ 3 - 0
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)

+ 155 - 0
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:   ]

+ 16 - 0
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<NodeKind::SelfTypeName, Lex::SelfTypeIdentifierTokenIndex>;
+
+// `.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<IdentifierName, SelfTypeName> name;
+};
+
 // Choice nodes
 // ------------