Skip to content

Fix child name validation failures found by testMultipleKeywordChoicesNaming #1857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,8 @@ public let ATTRIBUTE_NODES: [Node] = [
isOptional: true
),
Child(
name: "AccessorKind",
name: "AccessorSpecifier",
deprecatedName: "AccessorKind",
kind: .token(choices: [.keyword(text: "get"), .keyword(text: "set")]),
documentation: "The accessor name.",
isOptional: true
Expand All @@ -369,7 +370,8 @@ public let ATTRIBUTE_NODES: [Node] = [
isOptional: true
),
Child(
name: "DiffParams",
name: "Parameters",
deprecatedName: "DiffParams",
kind: .node(kind: .differentiabilityParamsClause),
isOptional: true
),
Expand Down Expand Up @@ -477,23 +479,27 @@ public let ATTRIBUTE_NODES: [Node] = [
"The arguments for the `@differentiable` attribute: an optional differentiability kind, an optional differentiability parameter clause, and an optional 'where' clause.",
children: [
Child(
name: "DiffKind",
name: "KindSpecifier",
deprecatedName: "DiffKind",
kind: .token(choices: [.keyword(text: "_forward"), .keyword(text: "reverse"), .keyword(text: "_linear")]),
isOptional: true
),
Child(
name: "DiffKindComma",
name: "KindSpecifierComma",
deprecatedName: "DiffKindComma",
kind: .token(choices: [.token(tokenKind: "CommaToken")]),
documentation: "The comma following the differentiability kind, if it exists.",
isOptional: true
),
Child(
name: "DiffParams",
name: "Parameters",
deprecatedName: "DiffParams",
kind: .node(kind: .differentiabilityParamsClause),
isOptional: true
),
Child(
name: "DiffParamsComma",
name: "ParametersComma",
deprecatedName: "DiffParamsComma",
kind: .token(choices: [.token(tokenKind: "CommaToken")]),
documentation: "The comma following the differentiability parameters clause, if it exists.",
isOptional: true
Expand Down
17 changes: 11 additions & 6 deletions CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ public let DECL_NODES: [Node] = [
isOptional: true
),
Child(
name: "AccessorKind",
name: "AccessorSpecifier",
deprecatedName: "AccessorKind",
kind: .token(choices: [
.keyword(text: "get"),
.keyword(text: "set"),
Expand Down Expand Up @@ -1213,7 +1214,8 @@ public let DECL_NODES: [Node] = [
documentation: "The `import` keyword for this declaration."
),
Child(
name: "ImportKind",
name: "ImportKindSpecifier",
deprecatedName: "ImportKind",
kind: .token(choices: [
.keyword(text: "typealias"),
.keyword(text: "struct"),
Expand Down Expand Up @@ -1570,7 +1572,8 @@ public let DECL_NODES: [Node] = [
],
children: [
Child(
name: "Fixity",
name: "FixitySpecifier",
deprecatedName: "Fixity",
kind: .token(choices: [.keyword(text: "prefix"), .keyword(text: "postfix"), .keyword(text: "infix")]),
nameForDiagnostics: "fixity",
documentation: "The fixity applied to the 'operator' declaration."
Expand Down Expand Up @@ -1785,7 +1788,8 @@ public let DECL_NODES: [Node] = [
kind: .token(choices: [.token(tokenKind: "ColonToken")])
),
Child(
name: "Flag",
name: "Value",
deprecatedName: "Flag",
kind: .token(choices: [.keyword(text: "true"), .keyword(text: "false")]),
documentation:
"When true, an operator in the corresponding precedence group uses the same grouping rules during optional chaining as the assignment operators from the standard library. Otherwise, operators in the precedence group follows the same optional chaining rules as operators that don't perform assignment."
Expand Down Expand Up @@ -1919,7 +1923,7 @@ public let DECL_NODES: [Node] = [
documentation: "Specify the new precedence group's relation to existing precedence groups.",
children: [
Child(
name: "HigherThanOrLowerThanKeyword",
name: "HigherThanOrLowerThanLabel",
deprecatedName: "HigherThanOrLowerThan",
kind: .token(choices: [.keyword(text: "higherThan"), .keyword(text: "lowerThan")]),
documentation: "The relation to specified other precedence groups.",
Expand Down Expand Up @@ -2350,7 +2354,8 @@ public let DECL_NODES: [Node] = [
isOptional: true
),
Child(
name: "BindingKeyword",
name: "BindingSpecifier",
deprecatedName: "BindingKeyword",
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var"), .keyword(text: "inout")])
),
Child(
Expand Down
6 changes: 4 additions & 2 deletions CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ public let EXPR_NODES: [Node] = [
nameForDiagnostics: "bool literal",
children: [
Child(
name: "BooleanLiteral",
name: "Literal",
deprecatedName: "BooleanLiteral",
kind: .token(choices: [.keyword(text: "true"), .keyword(text: "false")])
)
]
Expand Down Expand Up @@ -1221,7 +1222,8 @@ public let EXPR_NODES: [Node] = [
nameForDiagnostics: "'consume' expression",
children: [
Child(
name: "MoveKeyword",
name: "ConsumeKeyword",
deprecatedName: "MoveKeyword",
kind: .token(choices: [.keyword(text: "_move"), .keyword(text: "consume")])
),
Child(
Expand Down
3 changes: 2 additions & 1 deletion CodeGeneration/Sources/SyntaxSupport/PatternNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ public let PATTERN_NODES: [Node] = [
nameForDiagnostics: "value binding pattern",
children: [
Child(
name: "BindingKeyword",
name: "BindingSpecifier",
deprecatedName: "BindingKeyword",
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var"), .keyword(text: "inout")])
),
Child(
Expand Down
3 changes: 2 additions & 1 deletion CodeGeneration/Sources/SyntaxSupport/StmtNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,8 @@ public let STMT_NODES: [Node] = [
nameForDiagnostics: "optional binding",
children: [
Child(
name: "BindingKeyword",
name: "BindingSpecifier",
deprecatedName: "BindingKeyword",
kind: .token(choices: [.keyword(text: "let"), .keyword(text: "var"), .keyword(text: "inout")])
),
Child(
Expand Down
3 changes: 2 additions & 1 deletion CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,8 @@ public let TYPE_NODES: [Node] = [
kind: .token(choices: [.token(tokenKind: "PeriodToken")])
),
Child(
name: "TypeOrProtocol",
name: "MetatypeSpecifier",
deprecatedName: "TypeOrProtocol",
kind: .token(choices: [.keyword(text: "Type"), .keyword(text: "Protocol")])
),
]
Expand Down
122 changes: 66 additions & 56 deletions CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ fileprivate extension Child {
func hasSameType(as other: Child) -> Bool {
return name == other.name && kind.hasSameType(as: other.kind) && isOptional == other.isOptional
}

func isFollowedByColonToken(in node: LayoutNode) -> Bool {
guard let childIndex = node.children.firstIndex(where: { $0.name == self.name }) else {
preconditionFailure("\(self.name) is not a child of \(node.kind.syntaxType)")
}
guard childIndex + 2 < node.children.count else {
return false
}
if case .token(choices: [.token(tokenKind: "ColonToken")], _, _) = node.children[childIndex + 2].kind {
return true
} else {
return false
}
}
}

class ValidateSyntaxNodes: XCTestCase {
Expand Down Expand Up @@ -102,13 +116,13 @@ class ValidateSyntaxNodes: XCTestCase {
/// Implementation detail of `testSingleTokenChoiceChildNaming`, validating a single child.
///
/// - Returns: A failure message if validation failed, otherwise `nil`
private func validateSingleTokenChoiceChild(child: Child, childIndex: Int, in node: LayoutNode) -> String? {
private func validateSingleTokenChoiceChild(child: Child, in node: LayoutNode) -> String? {
guard case .token(choices: let tokenChoices, _, _) = child.kind, let choice = tokenChoices.only else {
return nil
}
switch choice {
case .keyword(text: let keyword):
if childIndex + 2 < node.children.count, case .token(choices: [.token(tokenKind: "ColonToken")], _, _) = node.children[childIndex + 2].kind {
if child.isFollowedByColonToken(in: node) {
if child.name != "\(keyword.withFirstCharacterUppercased)Label" {
return
"child '\(child.name)' has a single keyword as its only token choice and is followed by a colon. It should thus be named '\(keyword.withFirstCharacterUppercased)Label'"
Expand Down Expand Up @@ -148,8 +162,8 @@ class ValidateSyntaxNodes: XCTestCase {
func testSingleTokenChoiceChildNaming() {
var failures: [ValidationFailure] = []
for node in SYNTAX_NODES.compactMap(\.layoutNode) {
for (childIndex, child) in node.children.enumerated() {
if let failureMessage = validateSingleTokenChoiceChild(child: child, childIndex: childIndex, in: node) {
for child in node.children {
if let failureMessage = validateSingleTokenChoiceChild(child: child, in: node) {
failures.append(ValidationFailure(node: node.kind, message: failureMessage))
}
}
Expand Down Expand Up @@ -189,11 +203,11 @@ class ValidateSyntaxNodes: XCTestCase {
// If there are two tokens of the same kind in a node, we can't follow the naming rule without conflict
ValidationFailure(
node: .differentiableAttributeArguments,
message: "child 'DiffKindComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'"
message: "child 'KindSpecifierComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'"
),
ValidationFailure(
node: .differentiableAttributeArguments,
message: "child 'DiffParamsComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'"
message: "child 'ParametersComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'"
),
ValidationFailure(
node: .poundSourceLocationArgs,
Expand Down Expand Up @@ -292,79 +306,75 @@ class ValidateSyntaxNodes: XCTestCase {
var failures: [ValidationFailure] = []
for node in SYNTAX_NODES.compactMap(\.layoutNode) {
for child in node.children {
if case .token(choices: let tokenChoices, _, _) = child.kind,
tokenChoices.count > 1, // single token choices are handled by `validateSingleTokenChoiceChildNaming`
tokenChoices.allSatisfy({ $0.isKeyword }),
!child.name.hasSuffix("Keyword")
{
failures.append(
ValidationFailure(
node: node.kind,
message: "child '\(child.name)' only has keywords as its token choices and should thus and with 'Keyword'"
)
)
guard case .token(choices: let tokenChoices, _, _) = child.kind,
tokenChoices.count > 1,
tokenChoices.allSatisfy({ $0.isKeyword })
else {
// Not a child with only keyword choices
// Single token choices are handled by `validateSingleTokenChoiceChildNaming`
continue
}
var failureMessage: String?
if child.isFollowedByColonToken(in: node) {
if !child.name.hasSuffix("Label") {
failureMessage = "child '\(child.name)' only has keywords as its token choices, is followed by a colon and should thus end with 'Label'"
}
} else {
if !child.name.hasSuffix("Specifier") {
failureMessage = "child '\(child.name)' only has keywords as its token choices and should thus end with 'Specifier'"
}
}
if let failureMessage {
failures.append(ValidationFailure(node: node.kind, message: failureMessage))
}
}
}

assertFailuresMatchXFails(
failures,
expectedFailures: [
ValidationFailure(node: .accessorDecl, message: "child 'AccessorKind' only has keywords as its token choices and should thus and with 'Keyword'"),
ValidationFailure(node: .attributedType, message: "child 'Specifier' only has keywords as its token choices and should thus and with 'Keyword'"),
// MARK: Only one non-deprecated keyword
ValidationFailure(
node: .availabilityLabeledArgument,
message: "child 'Label' only has keywords as its token choices and should thus and with 'Keyword'"
node: .discardStmt,
message: "child 'DiscardKeyword' only has keywords as its token choices and should thus end with 'Specifier'"
// DiscardKeyword can be 'discard' or '_forget' and '_forget' is deprecated
),
ValidationFailure(
node: .booleanLiteralExpr,
message: "child 'BooleanLiteral' only has keywords as its token choices and should thus and with 'Keyword'"
),
ValidationFailure(node: .canImportVersionInfo, message: "child 'Label' only has keywords as its token choices and should thus and with 'Keyword'"),
ValidationFailure(
node: .closureCaptureItemSpecifier,
message: "child 'Specifier' only has keywords as its token choices and should thus and with 'Keyword'"
),
ValidationFailure(
node: .closureCaptureItemSpecifier,
message: "child 'Detail' only has keywords as its token choices and should thus and with 'Keyword'"
node: .moveExpr,
message: "child 'ConsumeKeyword' only has keywords as its token choices and should thus end with 'Specifier'"
// ConsumeKeyword can be 'consume' or '_move' and '_move' is deprecated
),

// MARK: Conceptually a value, not a specifier
ValidationFailure(
node: .constrainedSugarType,
message: "child 'SomeOrAnySpecifier' only has keywords as its token choices and should thus and with 'Keyword'"
),
ValidationFailure(node: .declModifier, message: "child 'Name' only has keywords as its token choices and should thus and with 'Keyword'"),
ValidationFailure(
node: .derivativeRegistrationAttributeArguments,
message: "child 'AccessorKind' only has keywords as its token choices and should thus and with 'Keyword'"
node: .booleanLiteralExpr,
message: "child 'Literal' only has keywords as its token choices and should thus end with 'Specifier'"
// TrueOrFalseKeyword would be a stupid name here
),
ValidationFailure(
node: .differentiableAttributeArguments,
message: "child 'DiffKind' only has keywords as its token choices and should thus and with 'Keyword'"
node: .precedenceGroupAssignment,
message: "child 'Value' only has keywords as its token choices and should thus end with 'Specifier'"
),
ValidationFailure(
node: .documentationAttributeArgument,
message: "child 'Label' only has keywords as its token choices and should thus and with 'Keyword'"
node: .precedenceGroupAssociativity,
message: "child 'Value' only has keywords as its token choices and should thus end with 'Specifier'"
),

// MARK: Miscellaneous
// 'weak' or 'unowned' are already the specifier, this is the detail in parens
ValidationFailure(
node: .functionEffectSpecifiers,
message: "child 'AsyncSpecifier' only has keywords as its token choices and should thus and with 'Keyword'"
node: .closureCaptureItemSpecifier,
message: "child 'Detail' only has keywords as its token choices and should thus end with 'Specifier'"
),
// This really is the modifier name and not a specifier
ValidationFailure(
node: .functionEffectSpecifiers,
message: "child 'ThrowsSpecifier' only has keywords as its token choices and should thus and with 'Keyword'"
node: .declModifier,
message: "child 'Name' only has keywords as its token choices and should thus end with 'Specifier'"
),
ValidationFailure(node: .importDecl, message: "child 'ImportKind' only has keywords as its token choices and should thus and with 'Keyword'"),
// Conceptually, this isn't a specifier, it's more like a type inheritance
ValidationFailure(
node: .layoutRequirement,
message: "child 'LayoutConstraint' only has keywords as its token choices and should thus and with 'Keyword'"
),
ValidationFailure(node: .metatypeType, message: "child 'TypeOrProtocol' only has keywords as its token choices and should thus and with 'Keyword'"),
ValidationFailure(node: .operatorDecl, message: "child 'Fixity' only has keywords as its token choices and should thus and with 'Keyword'"),
ValidationFailure(node: .precedenceGroupAssignment, message: "child 'Flag' only has keywords as its token choices and should thus and with 'Keyword'"),
ValidationFailure(
node: .precedenceGroupAssociativity,
message: "child 'Value' only has keywords as its token choices and should thus and with 'Keyword'"
message: "child 'LayoutConstraint' only has keywords as its token choices and should thus end with 'Specifier'"
),
]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ extension SyntaxClassification {
return (.operatorIdentifier, false)
case \PrecedenceGroupAssociativitySyntax.associativityLabel:
return (.keyword, false)
case \PrecedenceGroupRelationSyntax.higherThanOrLowerThanKeyword:
case \PrecedenceGroupRelationSyntax.higherThanOrLowerThanLabel:
return (.keyword, false)
case \SimpleTypeIdentifierSyntax.name:
return (.typeIdentifier, false)
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftOperators/OperatorTable+Semantics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension PrecedenceGroup {
switch attr {
// Relation (lowerThan, higherThan)
case .precedenceGroupRelation(let relation):
let isLowerThan = relation.higherThanOrLowerThanKeyword.text == "lowerThan"
let isLowerThan = relation.higherThanOrLowerThanLabel.text == "lowerThan"
for otherGroup in relation.otherNames {
let otherGroupName = otherGroup.name.text
let relationKind: PrecedenceRelation.Kind =
Expand All @@ -40,7 +40,7 @@ extension PrecedenceGroup {

// Assignment
case .precedenceGroupAssignment(let assignment):
self.assignment = assignment.flag.text == "true"
self.assignment = assignment.value.text == "true"

// Associativity
case .precedenceGroupAssociativity(let associativity):
Expand Down Expand Up @@ -68,7 +68,7 @@ extension Operator {
/// TODO: This ignores all semantic errors.
init(from syntax: OperatorDeclSyntax) {
self.syntax = syntax
kind = OperatorKind(rawValue: syntax.fixity.text) ?? .infix
kind = OperatorKind(rawValue: syntax.fixitySpecifier.text) ?? .infix

name = syntax.identifier.text

Expand Down
Loading