Skip to content

Commit e591f32

Browse files
committed
Add diagnostic for wrong specialize label
1 parent 65ad81a commit e591f32

File tree

6 files changed

+94
-10
lines changed

6 files changed

+94
-10
lines changed

CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,15 @@ public let ATTRIBUTE_NODES: [Node] = [
656656
children: [
657657
Child(
658658
name: "Label",
659-
kind: .node(kind: .token),
659+
kind: .token(choices: [
660+
.keyword(text: "target"),
661+
.keyword(text: "availability"),
662+
.keyword(text: "exported"),
663+
.keyword(text: "kind"),
664+
.keyword(text: "spi"),
665+
.keyword(text: "spiModule"),
666+
.keyword(text: "available"),
667+
]),
660668
nameForDiagnostics: "label",
661669
documentation: "The label of the argument"
662670
),

Sources/SwiftParser/Attributes.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,8 @@ extension Parser {
816816
elements.append(
817817
.labeledSpecializeEntry(
818818
RawLabeledSpecializeEntrySyntax(
819-
label: ident,
819+
RawUnexpectedNodesSyntax([ident], arena: self.arena),
820+
label: RawTokenSyntax(missing: .identifier, arena: self.arena),
820821
unexpectedBeforeColon,
821822
colon: colon,
822823
value: valueLabel,

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,33 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11201120
return .visitChildren
11211121
}
11221122

1123+
public override func visit(_ node: LabeledSpecializeEntrySyntax) -> SyntaxVisitorContinueKind {
1124+
if shouldSkip(node) {
1125+
return .skipChildren
1126+
}
1127+
1128+
if let unexpectedIdentifier = node.unexpectedBeforeLabel?.onlyPresentToken(where: { $0.tokenKind.isIdentifier }) {
1129+
addDiagnostic(
1130+
unexpectedIdentifier,
1131+
UnknownParameterError(
1132+
parameter: unexpectedIdentifier,
1133+
validParameters: [
1134+
.keyword(.target),
1135+
.keyword(.availability),
1136+
.keyword(.exported),
1137+
.keyword(.kind),
1138+
.keyword(.spi),
1139+
.keyword(.spiModule),
1140+
.keyword(.available),
1141+
]
1142+
),
1143+
handledNodes: [unexpectedIdentifier.id, node.label.id]
1144+
)
1145+
}
1146+
1147+
return .visitChildren
1148+
}
1149+
11231150
public override func visit(_ node: MacroExpansionDeclSyntax) -> SyntaxVisitorContinueKind {
11241151
if shouldSkip(node) {
11251152
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,23 @@ public struct UnknownDirectiveError: ParserError {
512512
}
513513
}
514514

515+
public struct UnknownParameterError: ParserError {
516+
public let parameter: TokenSyntax
517+
public let validParameters: [TokenSyntax]
518+
519+
public var message: String {
520+
var message = "unknown parameter '\(parameter.description)'"
521+
522+
if let parentTypeName = parameter.ancestorOrSelf(mapping: { $0.nodeTypeNameForDiagnostics(allowBlockNames: false) }) {
523+
message += " in \(parentTypeName)"
524+
}
525+
526+
message += "; valid parameters \(nodesDescription(validParameters, format: true))"
527+
528+
return message
529+
}
530+
}
531+
515532
// MARK: - Notes (please sort alphabetically)
516533

517534
public struct EffectSpecifierDeclaredHere: ParserNote {

Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1648,7 +1648,15 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) {
16481648
case .labeledSpecializeEntry:
16491649
assert(layout.count == 9)
16501650
assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self))
1651-
assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self))
1651+
assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [
1652+
.keyword("target"),
1653+
.keyword("availability"),
1654+
.keyword("exported"),
1655+
.keyword("kind"),
1656+
.keyword("spi"),
1657+
.keyword("spiModule"),
1658+
.keyword("available")
1659+
]))
16521660
assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self))
16531661
assertNoError(kind, 3, verify(layout[3], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.colon)]))
16541662
assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self))

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,41 +73,64 @@ final class AttributeTests: XCTestCase {
7373
func testMissingClosingParenToAttribute() {
7474
assertParse(
7575
"""
76-
@_specializeℹ️(e1️⃣
76+
@_specializeℹ️(1️⃣e2️⃣
7777
""",
7878
diagnostics: [
7979
DiagnosticSpec(
80+
locationMarker: "1️⃣",
81+
message: "unknown parameter 'e' in token; valid parameters 'target', 'availability', 'exported', 'kind', 'spi', 'spiModule', and 'available'"
82+
),
83+
DiagnosticSpec(
84+
locationMarker: "2️⃣",
8085
message: "expected ':' in attribute argument",
8186
fixIts: ["insert ':'"]
8287
),
8388
DiagnosticSpec(
89+
locationMarker: "2️⃣",
8490
message: "expected ')' to end attribute",
8591
notes: [NoteSpec(message: "to match this opening '('")],
8692
fixIts: ["insert ')'"]
8793
),
8894
DiagnosticSpec(
95+
locationMarker: "2️⃣",
8996
message: "expected declaration after attribute",
9097
fixIts: ["insert declaration"]
9198
),
9299
],
93100
fixedSource: """
94-
@_specialize(e:) <#declaration#>
101+
@_specialize(e <#identifier#>:) <#declaration#>
95102
"""
96103
)
97104
}
98105

99106
func testMultipleInvalidSpecializeParams() {
100107
assertParse(
101108
"""
102-
@_specialize(e1️⃣, exported2️⃣)3️⃣
109+
@_specialize(1️⃣e2️⃣, exported3️⃣)4️⃣
103110
""",
104111
diagnostics: [
105-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected ':' in attribute argument", fixIts: ["insert ':'"]),
106-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ': false' in attribute argument", fixIts: ["insert ': false'"]),
107-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected declaration after attribute", fixIts: ["insert declaration"]),
112+
DiagnosticSpec(
113+
locationMarker: "1️⃣",
114+
message: "unknown parameter 'e' in token; valid parameters 'target', 'availability', 'exported', 'kind', 'spi', 'spiModule', and 'available'"
115+
),
116+
DiagnosticSpec(
117+
locationMarker: "2️⃣",
118+
message: "expected ':' in attribute argument",
119+
fixIts: ["insert ':'"]
120+
),
121+
DiagnosticSpec(
122+
locationMarker: "3️⃣",
123+
message: "expected ': false' in attribute argument",
124+
fixIts: ["insert ': false'"]
125+
),
126+
DiagnosticSpec(
127+
locationMarker: "4️⃣",
128+
message: "expected declaration after attribute",
129+
fixIts: ["insert declaration"]
130+
),
108131
],
109132
fixedSource: """
110-
@_specialize(e:, exported: false) <#declaration#>
133+
@_specialize(e <#identifier#>:, exported: false) <#declaration#>
111134
"""
112135
)
113136
}

0 commit comments

Comments
 (0)