Skip to content

Commit 717ac51

Browse files
committed
Add diagnostic for wrong LabeledSpecializeEntry label
1 parent 391df89 commit 717ac51

File tree

4 files changed

+87
-54
lines changed

4 files changed

+87
-54
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -638,48 +638,14 @@ extension Parser {
638638
}
639639

640640
extension Parser {
641-
enum SpecializeParameter: TokenSpecSet {
642-
case target
643-
case availability
644-
case exported
645-
case kind
646-
case spi
647-
case spiModule
648-
case available
649-
650-
init?(lexeme: Lexer.Lexeme) {
651-
switch PrepareForKeywordMatch(lexeme) {
652-
case TokenSpec(.target): self = .target
653-
case TokenSpec(.availability): self = .availability
654-
case TokenSpec(.exported): self = .exported
655-
case TokenSpec(.kind): self = .kind
656-
case TokenSpec(.spi): self = .spi
657-
case TokenSpec(.spiModule): self = .spiModule
658-
case TokenSpec(.available): self = .available
659-
default: return nil
660-
}
661-
}
662-
663-
var spec: TokenSpec {
664-
switch self {
665-
case .target: return .keyword(.target)
666-
case .availability: return .keyword(.availability)
667-
case .exported: return .keyword(.exported)
668-
case .kind: return .keyword(.kind)
669-
case .spi: return .keyword(.spi)
670-
case .spiModule: return .keyword(.spiModule)
671-
case .available: return .keyword(.available)
672-
}
673-
}
674-
}
675641
mutating func parseSpecializeAttributeSpecList() -> RawSpecializeAttributeSpecListSyntax {
676642
var elements = [RawSpecializeAttributeSpecListSyntax.Element]()
677643
// Parse optional "exported" and "kind" labeled parameters.
678644
var loopProgress = LoopProgressCondition()
679645
while !self.at(.eof, .rightParen, .keyword(.where)) && loopProgress.evaluate(currentToken) {
680-
switch self.at(anyIn: SpecializeParameter.self) {
646+
switch self.canRecoverTo(anyIn: LabeledSpecializeEntrySyntax.LabelOptions.self) {
681647
case (.target, let handle)?:
682-
let ident = self.eat(handle)
648+
let (unexpectedBeforeLabel, label) = self.eat(handle)
683649
let (unexpectedBeforeColon, colon) = self.expect(.colon)
684650
let (targetFunction, args) = self.parseDeclNameRef([.zeroArgCompoundNames, .keywordsUsingSpecialNames, .operators])
685651
let declName = RawDeclNameSyntax(
@@ -691,7 +657,8 @@ extension Parser {
691657
elements.append(
692658
.targetFunctionEntry(
693659
RawTargetFunctionEntrySyntax(
694-
targetLabel: ident,
660+
unexpectedBeforeLabel,
661+
targetLabel: label,
695662
unexpectedBeforeColon,
696663
colon: colon,
697664
declname: declName,
@@ -701,7 +668,7 @@ extension Parser {
701668
)
702669
)
703670
case (.availability, let handle)?:
704-
let ident = self.eat(handle)
671+
let (unexpectedBeforeLabel, label) = self.eat(handle)
705672
let (unexpectedBeforeColon, colon) = self.expect(.colon)
706673
let availability = self.parseAvailabilitySpecList()
707674
let (unexpectedBeforeSemi, semi) = self.expect(.semicolon)
@@ -719,7 +686,7 @@ extension Parser {
719686
)
720687
)
721688
case (.available, let handle)?:
722-
let ident = self.eat(handle)
689+
let (unexpectedBeforeLabel, label) = self.eat(handle)
723690
let (unexpectedBeforeColon, colon) = self.expect(.colon)
724691
// FIXME: I have no idea what this is supposed to be, but the Syntax
725692
// tree only allows us to insert a token so we'll take anything.
@@ -728,7 +695,8 @@ extension Parser {
728695
elements.append(
729696
.labeledSpecializeEntry(
730697
RawLabeledSpecializeEntrySyntax(
731-
label: ident,
698+
unexpectedBeforeLabel,
699+
label: label,
732700
unexpectedBeforeColon,
733701
colon: colon,
734702
value: available,
@@ -738,14 +706,15 @@ extension Parser {
738706
)
739707
)
740708
case (.exported, let handle)?:
741-
let ident = self.eat(handle)
709+
let (unexpectedBeforeLabel, label) = self.eat(handle)
742710
let (unexpectedBeforeColon, colon) = self.expect(.colon)
743711
let (unexpectedBeforeValue, value) = self.expect(.keyword(.true), .keyword(.false), default: .keyword(.false))
744712
let comma = self.consume(if: .comma)
745713
elements.append(
746714
.labeledSpecializeEntry(
747715
RawLabeledSpecializeEntrySyntax(
748-
label: ident,
716+
unexpectedBeforeLabel,
717+
label: label,
749718
unexpectedBeforeColon,
750719
colon: colon,
751720
unexpectedBeforeValue,
@@ -756,14 +725,15 @@ extension Parser {
756725
)
757726
)
758727
case (.kind, let handle)?:
759-
let ident = self.eat(handle)
728+
let (unexpectedBeforeLabel, label) = self.eat(handle)
760729
let (unexpectedBeforeColon, colon) = self.expect(.colon)
761730
let valueLabel = self.parseAnyIdentifier()
762731
let comma = self.consume(if: .comma)
763732
elements.append(
764733
.labeledSpecializeEntry(
765734
RawLabeledSpecializeEntrySyntax(
766-
label: ident,
735+
unexpectedBeforeLabel,
736+
label: label,
767737
unexpectedBeforeColon,
768738
colon: colon,
769739
value: valueLabel,
@@ -774,14 +744,15 @@ extension Parser {
774744
)
775745
case (.spiModule, let handle)?,
776746
(.spi, let handle)?:
777-
let ident = self.eat(handle)
747+
let (unexpectedBeforeLabel, label) = self.eat(handle)
778748
let (unexpectedBeforeColon, colon) = self.expect(.colon)
779749
let valueLabel = self.consumeAnyToken()
780750
let comma = self.consume(if: .comma)
781751
elements.append(
782752
.labeledSpecializeEntry(
783753
RawLabeledSpecializeEntrySyntax(
784-
label: ident,
754+
unexpectedBeforeLabel,
755+
label: label,
785756
unexpectedBeforeColon,
786757
colon: colon,
787758
value: valueLabel,
@@ -798,7 +769,8 @@ extension Parser {
798769
elements.append(
799770
.labeledSpecializeEntry(
800771
RawLabeledSpecializeEntrySyntax(
801-
label: ident,
772+
RawUnexpectedNodesSyntax([ident], arena: self.arena),
773+
label: RawTokenSyntax(missing: .identifier, arena: self.arena),
802774
unexpectedBeforeColon,
803775
colon: colon,
804776
value: valueLabel,

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,25 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
12261226
return .visitChildren
12271227
}
12281228

1229+
public override func visit(_ node: LabeledSpecializeEntrySyntax) -> SyntaxVisitorContinueKind {
1230+
if shouldSkip(node) {
1231+
return .skipChildren
1232+
}
1233+
1234+
if let unexpectedIdentifier = node.unexpectedBeforeLabel?.onlyPresentToken(where: { $0.tokenKind.isIdentifier }) {
1235+
addDiagnostic(
1236+
unexpectedIdentifier,
1237+
UnknownParameterError(
1238+
parameter: unexpectedIdentifier,
1239+
validParameters: LabeledSpecializeEntrySyntax.LabelOptions.allCases.map { $0.tokenSyntax }
1240+
),
1241+
handledNodes: [unexpectedIdentifier.id, node.label.id]
1242+
)
1243+
}
1244+
1245+
return .visitChildren
1246+
}
1247+
12291248
public override func visit(_ node: MacroExpansionDeclSyntax) -> SyntaxVisitorContinueKind {
12301249
if shouldSkip(node) {
12311250
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,23 @@ public struct UnknownDirectiveError: ParserError {
518518
}
519519
}
520520

521+
public struct UnknownParameterError: ParserError {
522+
public let parameter: TokenSyntax
523+
public let validParameters: [TokenSyntax]
524+
525+
public var message: String {
526+
var message = "unknown parameter '\(parameter.text)'"
527+
528+
if let parentTypeName = parameter.parent?.ancestorOrSelf(mapping: { $0.nodeTypeNameForDiagnostics(allowBlockNames: false) }) {
529+
message += " in \(parentTypeName)"
530+
}
531+
532+
message += "; valid parameters are \(nodesDescription(validParameters, format: true))"
533+
534+
return message
535+
}
536+
}
537+
521538
// MARK: - Notes (please sort alphabetically)
522539

523540
public struct EffectSpecifierDeclaredHere: ParserNote {

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,41 +73,66 @@ 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:
82+
"unknown parameter 'e' in attribute argument; valid parameters are 'target', 'availability', 'exported', 'kind', 'spi', 'spiModule', and 'available'"
83+
),
84+
DiagnosticSpec(
85+
locationMarker: "2️⃣",
8086
message: "expected ':' in attribute argument",
8187
fixIts: ["insert ':'"]
8288
),
8389
DiagnosticSpec(
90+
locationMarker: "2️⃣",
8491
message: "expected ')' to end attribute",
8592
notes: [NoteSpec(message: "to match this opening '('")],
8693
fixIts: ["insert ')'"]
8794
),
8895
DiagnosticSpec(
96+
locationMarker: "2️⃣",
8997
message: "expected declaration after attribute",
9098
fixIts: ["insert declaration"]
9199
),
92100
],
93101
fixedSource: """
94-
@_specialize(e:) <#declaration#>
102+
@_specialize(e <#identifier#>:) <#declaration#>
95103
"""
96104
)
97105
}
98106

99107
func testMultipleInvalidSpecializeParams() {
100108
assertParse(
101109
"""
102-
@_specialize(e1️⃣, exported2️⃣)3️⃣
110+
@_specialize(1️⃣e2️⃣, exported3️⃣)4️⃣
103111
""",
104112
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"]),
113+
DiagnosticSpec(
114+
locationMarker: "1️⃣",
115+
message:
116+
"unknown parameter 'e' in attribute argument; valid parameters are 'target', 'availability', 'exported', 'kind', 'spi', 'spiModule', and 'available'"
117+
),
118+
DiagnosticSpec(
119+
locationMarker: "2️⃣",
120+
message: "expected ':' in attribute argument",
121+
fixIts: ["insert ':'"]
122+
),
123+
DiagnosticSpec(
124+
locationMarker: "3️⃣",
125+
message: "expected ': false' in attribute argument",
126+
fixIts: ["insert ': false'"]
127+
),
128+
DiagnosticSpec(
129+
locationMarker: "4️⃣",
130+
message: "expected declaration after attribute",
131+
fixIts: ["insert declaration"]
132+
),
108133
],
109134
fixedSource: """
110-
@_specialize(e:, exported: false) <#declaration#>
135+
@_specialize(e <#identifier#>:, exported: false) <#declaration#>
111136
"""
112137
)
113138
}

0 commit comments

Comments
 (0)