Skip to content

Commit

Permalink
Parse support for where operator (#4275)
Browse files Browse the repository at this point in the history
Includes support for the `impls`, `=`, and `==` requirement operators to
the right of a `where`, but `and` to allow multiple requirements is
still a TODO.

---------

Co-authored-by: Josh L <josh11b@users.noreply.github.com>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
  • Loading branch information
3 people authored Sep 11, 2024
1 parent 8fa173b commit c33c9a0
Show file tree
Hide file tree
Showing 19 changed files with 784 additions and 38 deletions.
23 changes: 23 additions & 0 deletions toolchain/check/handle_where.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,27 @@ auto HandleParseNode(Context& context, Parse::DesignatorExprId node_id)
return context.TODO(node_id, "HandleDesignatorExpr");
}

auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
return context.TODO(node_id, "HandleWhereOperand");
}

auto HandleParseNode(Context& context, Parse::RequirementEqualId node_id)
-> bool {
return context.TODO(node_id, "HandleRequirementEqual");
}

auto HandleParseNode(Context& context, Parse::RequirementEqualEqualId node_id)
-> bool {
return context.TODO(node_id, "HandleRequirementEqualEqual");
}

auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
-> bool {
return context.TODO(node_id, "HandleRequirementImpls");
}

auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool {
return context.TODO(node_id, "HandleWhereExpr");
}

} // namespace Carbon::Check
5 changes: 5 additions & 0 deletions toolchain/check/node_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,9 @@ class NodeStack {
case Parse::NodeKind::PrivateModifier:
case Parse::NodeKind::ProtectedModifier:
case Parse::NodeKind::RealLiteral:
case Parse::NodeKind::RequirementEqual:
case Parse::NodeKind::RequirementEqualEqual:
case Parse::NodeKind::RequirementImpls:
case Parse::NodeKind::ReturnStatement:
case Parse::NodeKind::SelfTypeName:
case Parse::NodeKind::SelfTypeNameExpr:
Expand All @@ -641,6 +644,8 @@ class NodeStack {
case Parse::NodeKind::UnsignedIntTypeLiteral:
case Parse::NodeKind::VariableDecl:
case Parse::NodeKind::VirtualModifier:
case Parse::NodeKind::WhereExpr:
case Parse::NodeKind::WhereOperand:
case Parse::NodeKind::WhileStatement:
return Id::Kind::Invalid;
}
Expand Down
36 changes: 3 additions & 33 deletions toolchain/check/testdata/impl/fail_todo_impl_assoc_const.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

interface I { let T:! type; }

// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:16: ERROR: `impl` declarations must either end with a `;` or have a `{ ... }` block for a definition.
// CHECK:STDERR: fail_todo_impl_assoc_const.carbon:[[@LINE+3]]:14: ERROR: Semantics TODO: `HandleWhereOperand`.
// CHECK:STDERR: impl bool as I where .T = bool {}
// CHECK:STDERR: ^~~~~
// CHECK:STDERR: ^~~~~~~
impl bool as I where .T = bool {}

// CHECK:STDOUT: --- fail_todo_impl_assoc_const.carbon
Expand All @@ -27,35 +27,7 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: %Bool: %Bool.type = struct_value () [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Bool = %import_ref
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/operators
// CHECK:STDOUT: import Core//prelude/types
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
// CHECK:STDOUT: import Core//prelude/operators/as
// CHECK:STDOUT: import Core//prelude/operators/bitwise
// CHECK:STDOUT: import Core//prelude/operators/comparison
// CHECK:STDOUT: import Core//prelude/types/bool
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref: %Bool.type = import_ref Core//prelude/types/bool, inst+2, loaded [template = constants.%Bool]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .I = %I.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %I.decl: type = interface_decl @I [template = constants.%.1] {}
// CHECK:STDOUT: impl_decl @impl {
// CHECK:STDOUT: %bool.make_type: init type = call constants.%Bool() [template = bool]
// CHECK:STDOUT: %.loc16_6.1: type = value_of_initializer %bool.make_type [template = bool]
// CHECK:STDOUT: %.loc16_6.2: type = converted %bool.make_type, %.loc16_6.1 [template = bool]
// CHECK:STDOUT: %I.ref: type = name_ref I, %I.decl [template = constants.%.1]
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT: file {}
// CHECK:STDOUT:
// CHECK:STDOUT: interface @I {
// CHECK:STDOUT: %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self]
Expand All @@ -68,7 +40,5 @@ impl bool as I where .T = bool {}
// CHECK:STDOUT: witness = (%T)
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl: bool as %.1;
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Bool() -> type = "bool.make_type";
// CHECK:STDOUT:
3 changes: 3 additions & 0 deletions toolchain/diagnostics/diagnostic_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ CARBON_DIAGNOSTIC_KIND(ImplExpectedAs)
// Alias diagnostics.
CARBON_DIAGNOSTIC_KIND(ExpectedAliasInitializer)

// Where requirement diagnostics.
CARBON_DIAGNOSTIC_KIND(ExpectedRequirementOperator)

// ============================================================================
// Semantics diagnostics
// ============================================================================
Expand Down
3 changes: 2 additions & 1 deletion toolchain/lex/token_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ CARBON_KEYWORD_TOKEN(Type, "type")
// Underscore is tokenized as a keyword because it's part of identifiers.
CARBON_KEYWORD_TOKEN(Underscore, "_")
CARBON_KEYWORD_TOKEN(Virtual, "virtual")
CARBON_KEYWORD_TOKEN(Where, "where")
CARBON_TOKEN_WITH_VIRTUAL_NODE(
CARBON_KEYWORD_TOKEN(Where, "where"))
CARBON_KEYWORD_TOKEN(While, "while")

// clang-format on
Expand Down
9 changes: 9 additions & 0 deletions toolchain/parse/handle_expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,15 @@ auto HandleExprLoop(Context& context) -> void {
state.state = State::ExprLoopForShortCircuitOperatorAsOr;
break;

// `where` also needs a virtual parse tree node, and parses its right
// argument in a mode where it can handle requirement operators like
// `impls` and `=`.
case Lex::TokenKind::Where:
context.AddNode(NodeKind::WhereOperand, state.token, state.has_error);
context.PushState(state, State::WhereFinish);
context.PushState(State::RequirementBegin);
return;

default:
state.state = State::ExprLoopForInfixOperator;
break;
Expand Down
83 changes: 83 additions & 0 deletions toolchain/parse/handle_requirement.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// 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/parse/context.h"
#include "toolchain/parse/handle.h"

namespace Carbon::Parse {

auto HandleRequirementBegin(Context& context) -> void {
context.PopAndDiscardState();
context.PushState(State::RequirementOperator);
context.PushStateForExpr(PrecedenceGroup::ForRequirements());
}

auto HandleRequirementOperator(Context& context) -> void {
auto state = context.PopState();

switch (context.PositionKind()) {
case Lex::TokenKind::Impls: {
break;
}
case Lex::TokenKind::Equal: {
break;
}
case Lex::TokenKind::EqualEqual: {
break;
}
default: {
if (!state.has_error) {
CARBON_DIAGNOSTIC(
ExpectedRequirementOperator, Error,
"Requirement should use `impls`, `=`, or `==` operator.");
context.emitter().Emit(*context.position(),
ExpectedRequirementOperator);
}
context.ReturnErrorOnState();
return;
}
}
state.token = context.Consume();
context.PushState(state, State::RequirementOperatorFinish);
context.PushStateForExpr(PrecedenceGroup::ForRequirements());
}

auto HandleRequirementOperatorFinish(Context& context) -> void {
auto state = context.PopState();

switch (auto token_kind = context.tokens().GetKind(state.token)) {
case Lex::TokenKind::Impls: {
context.AddNode(NodeKind::RequirementImpls, state.token, state.has_error);
break;
}
case Lex::TokenKind::Equal: {
context.AddNode(NodeKind::RequirementEqual, state.token, state.has_error);
break;
}
case Lex::TokenKind::EqualEqual: {
context.AddNode(NodeKind::RequirementEqualEqual, state.token,
state.has_error);
break;
}
default:
// RequirementOperatorFinish state is only pushed in
// HandleRequirementOperator on one of the three requirement operator
// tokens.
CARBON_FATAL() << "Unexpected token kind for requirement operator: "
<< token_kind;
return;
}
// TODO: Handle `and` token.
}

auto HandleWhereFinish(Context& context) -> void {
auto state = context.PopState();
if (state.has_error) {
context.ReturnErrorOnState();
}
context.AddNode(NodeKind::WhereExpr, state.token, state.has_error);
}

} // namespace Carbon::Parse
1 change: 1 addition & 0 deletions toolchain/parse/node_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ using AnyMemberAccessId =
using AnyModifierId = NodeIdInCategory<NodeCategory::Modifier>;
using AnyPatternId = NodeIdInCategory<NodeCategory::Pattern>;
using AnyStatementId = NodeIdInCategory<NodeCategory::Statement>;
using AnyRequirementId = NodeIdInCategory<NodeCategory::Requirement>;

// NodeId with kind that matches one of the `T::Kind`s.
template <typename... T>
Expand Down
5 changes: 5 additions & 0 deletions toolchain/parse/node_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ CARBON_PARSE_NODE_KIND(ShortCircuitOperatorOr)

CARBON_PARSE_NODE_KIND(SelfTypeName)
CARBON_PARSE_NODE_KIND(DesignatorExpr)
CARBON_PARSE_NODE_KIND(RequirementEqual)
CARBON_PARSE_NODE_KIND(RequirementEqualEqual)
CARBON_PARSE_NODE_KIND(RequirementImpls)
CARBON_PARSE_NODE_KIND(WhereExpr)
CARBON_PARSE_NODE_KIND(WhereOperand)

CARBON_PARSE_NODE_KIND(IfExprIf)
CARBON_PARSE_NODE_KIND(IfExprThen)
Expand Down
3 changes: 2 additions & 1 deletion toolchain/parse/node_kind.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ class NodeCategory : public Printable<NodeCategory> {
Pattern = 1 << 6,
Statement = 1 << 7,
IntConst = 1 << 8,
Requirement = 1 << 9,
None = 0,

LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/IntConst)
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/Requirement)
};

// Support implicit conversion so that the difference with the member enum is
Expand Down
14 changes: 12 additions & 2 deletions toolchain/parse/precedence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ enum PrecedenceLevel : int8_t {
// Type formation.
TypePrefix,
TypePostfix,
// `where` keyword.
Where,
// Casts.
As,
// Logical.
Expand Down Expand Up @@ -60,9 +62,9 @@ struct OperatorPriorityTable {
MarkHigherThan({Multiplicative}, {Additive});
MarkHigherThan(
{Additive, Modulo, BitwiseAnd, BitwiseOr, BitwiseXor, BitShift},
{Relational});
{Relational, Where});
MarkHigherThan({Relational, LogicalPrefix}, {LogicalAnd, LogicalOr});
MarkHigherThan({As, LogicalAnd, LogicalOr}, {If});
MarkHigherThan({As, LogicalAnd, LogicalOr, Where}, {If});
MarkHigherThan({If}, {Assignment});
MarkHigherThan({Assignment, IncrementDecrement}, {Lowest});

Expand Down Expand Up @@ -194,6 +196,10 @@ auto PrecedenceGroup::ForImplAs() -> PrecedenceGroup {
return PrecedenceGroup(As);
}

auto PrecedenceGroup::ForRequirements() -> PrecedenceGroup {
return PrecedenceGroup(Where);
}

auto PrecedenceGroup::ForLeading(Lex::TokenKind kind)
-> std::optional<PrecedenceGroup> {
switch (kind) {
Expand Down Expand Up @@ -289,6 +295,10 @@ auto PrecedenceGroup::ForTrailing(Lex::TokenKind kind, bool infix)
case Lex::TokenKind::As:
return Trailing{.level = As, .is_binary = true};

// Requirement operator.
case Lex::TokenKind::Where:
return Trailing{.level = Where, .is_binary = true};

// Prefix-only operators.
case Lex::TokenKind::Const:
case Lex::TokenKind::MinusMinus:
Expand Down
4 changes: 4 additions & 0 deletions toolchain/parse/precedence.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ class PrecedenceGroup {
// `impl` and `as`.
static auto ForImplAs() -> PrecedenceGroup;

// Get the precedence level at which to parse expressions in requirements
// after `where` or `require`.
static auto ForRequirements() -> PrecedenceGroup;

// Look up the operator information of the given prefix operator token, or
// return std::nullopt if the given token is not a prefix operator.
static auto ForLeading(Lex::TokenKind kind) -> std::optional<PrecedenceGroup>;
Expand Down
42 changes: 41 additions & 1 deletion toolchain/parse/state.def
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//
// The comments before each state describe the portion of the grammar that the
// state is implementing, by giving an example of each kind of token sequence
// that this state handles. In this example, `...` indicates a sequence of
// that this state handles. In these examples, `...` indicates a sequence of
// tokens handled by some other state, and `???` indicates a sequence of invalid
// tokens. A trailing `??? ;` indicates an attempt to skip to the end of the
// declaration, which may or may not actually find a `;` token.
Expand Down Expand Up @@ -614,6 +614,46 @@ CARBON_PARSE_STATE(IfExprFinishElse)
// (state done)
CARBON_PARSE_STATE(IfExprFinish)

// Handles the beginning of a requirement expression after a `where` operator in
// an expression.
// TODO: Also a `require` declaration?
//
// expr where ...
// ^
// 1. Expr
// 2. RequirementOperator
CARBON_PARSE_STATE(RequirementBegin)

// Handles a requirement operator in a `where` expression.
//
// expr where expr impls ...
// ^~~~
// expr where expr = ...
// ^
// expr where expr == ...
// ^~
// 1. Expr
// 2. RequirementOperatorFinish
CARBON_PARSE_STATE(RequirementOperator)

// Finishes a requirement operator in a `where` expression.
//
// expr where expr impls expr
// ^
// expr where expr = expr
// ^
// expr where expr == expr
// ^
// (state done)
CARBON_PARSE_STATE(RequirementOperatorFinish)

// Finishes an `where` expression.
//
// expr where requirement
// ^
// (state done)
CARBON_PARSE_STATE(WhereFinish)

// Handles the `;` for an expression statement, which is different from most
// keyword statements.
//
Expand Down
Loading

0 comments on commit c33c9a0

Please sign in to comment.