Skip to content

Commit 235f328

Browse files
committed
Add diagnostic for wrong LabeledSpecializeEntry label
1 parent 0675827 commit 235f328

File tree

4 files changed

+89
-55
lines changed

4 files changed

+89
-55
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -610,48 +610,14 @@ extension Parser {
610610
}
611611

612612
extension Parser {
613-
enum SpecializeParameter: TokenSpecSet {
614-
case target
615-
case availability
616-
case exported
617-
case kind
618-
case spi
619-
case spiModule
620-
case available
621-
622-
init?(lexeme: Lexer.Lexeme) {
623-
switch PrepareForKeywordMatch(lexeme) {
624-
case TokenSpec(.target): self = .target
625-
case TokenSpec(.availability): self = .availability
626-
case TokenSpec(.exported): self = .exported
627-
case TokenSpec(.kind): self = .kind
628-
case TokenSpec(.spi): self = .spi
629-
case TokenSpec(.spiModule): self = .spiModule
630-
case TokenSpec(.available): self = .available
631-
default: return nil
632-
}
633-
}
634-
635-
var spec: TokenSpec {
636-
switch self {
637-
case .target: return .keyword(.target)
638-
case .availability: return .keyword(.availability)
639-
case .exported: return .keyword(.exported)
640-
case .kind: return .keyword(.kind)
641-
case .spi: return .keyword(.spi)
642-
case .spiModule: return .keyword(.spiModule)
643-
case .available: return .keyword(.available)
644-
}
645-
}
646-
}
647613
mutating func parseSpecializeAttributeSpecList() -> RawSpecializeAttributeSpecListSyntax {
648614
var elements = [RawSpecializeAttributeSpecListSyntax.Element]()
649615
// Parse optional "exported" and "kind" labeled parameters.
650616
var loopProgress = LoopProgressCondition()
651617
while !self.at(.eof, .rightParen, .keyword(.where)) && loopProgress.evaluate(currentToken) {
652-
switch self.at(anyIn: SpecializeParameter.self) {
618+
switch self.canRecoverTo(anyIn: LabeledSpecializeEntrySyntax.LabelOptions.self) {
653619
case (.target, let handle)?:
654-
let ident = self.eat(handle)
620+
let (unexpectedBeforeLabel, label) = self.eat(handle)
655621
let (unexpectedBeforeColon, colon) = self.expect(.colon)
656622
let (targetFunction, args) = self.parseDeclNameRef([.zeroArgCompoundNames, .keywordsUsingSpecialNames, .operators])
657623
let declName = RawDeclNameSyntax(
@@ -663,7 +629,8 @@ extension Parser {
663629
elements.append(
664630
.targetFunctionEntry(
665631
RawTargetFunctionEntrySyntax(
666-
label: ident,
632+
unexpectedBeforeLabel,
633+
label: label,
667634
unexpectedBeforeColon,
668635
colon: colon,
669636
declname: declName,
@@ -673,14 +640,15 @@ extension Parser {
673640
)
674641
)
675642
case (.availability, let handle)?:
676-
let ident = self.eat(handle)
643+
let (unexpectedBeforeLabel, label) = self.eat(handle)
677644
let (unexpectedBeforeColon, colon) = self.expect(.colon)
678645
let availability = self.parseAvailabilitySpecList()
679646
let (unexpectedBeforeSemi, semi) = self.expect(.semicolon)
680647
elements.append(
681648
.availabilityEntry(
682649
RawAvailabilityEntrySyntax(
683-
label: ident,
650+
unexpectedBeforeLabel,
651+
label: label,
684652
unexpectedBeforeColon,
685653
colon: colon,
686654
availabilityArguments: availability,
@@ -691,7 +659,7 @@ extension Parser {
691659
)
692660
)
693661
case (.available, let handle)?:
694-
let ident = self.eat(handle)
662+
let (unexpectedBeforeLabel, label) = self.eat(handle)
695663
let (unexpectedBeforeColon, colon) = self.expect(.colon)
696664
// FIXME: I have no idea what this is supposed to be, but the Syntax
697665
// tree only allows us to insert a token so we'll take anything.
@@ -700,7 +668,8 @@ extension Parser {
700668
elements.append(
701669
.labeledSpecializeEntry(
702670
RawLabeledSpecializeEntrySyntax(
703-
label: ident,
671+
unexpectedBeforeLabel,
672+
label: label,
704673
unexpectedBeforeColon,
705674
colon: colon,
706675
value: available,
@@ -710,14 +679,15 @@ extension Parser {
710679
)
711680
)
712681
case (.exported, let handle)?:
713-
let ident = self.eat(handle)
682+
let (unexpectedBeforeLabel, label) = self.eat(handle)
714683
let (unexpectedBeforeColon, colon) = self.expect(.colon)
715684
let (unexpectedBeforeValue, value) = self.expect(.keyword(.true), .keyword(.false), default: .keyword(.false))
716685
let comma = self.consume(if: .comma)
717686
elements.append(
718687
.labeledSpecializeEntry(
719688
RawLabeledSpecializeEntrySyntax(
720-
label: ident,
689+
unexpectedBeforeLabel,
690+
label: label,
721691
unexpectedBeforeColon,
722692
colon: colon,
723693
unexpectedBeforeValue,
@@ -728,14 +698,15 @@ extension Parser {
728698
)
729699
)
730700
case (.kind, let handle)?:
731-
let ident = self.eat(handle)
701+
let (unexpectedBeforeLabel, label) = self.eat(handle)
732702
let (unexpectedBeforeColon, colon) = self.expect(.colon)
733703
let valueLabel = self.parseAnyIdentifier()
734704
let comma = self.consume(if: .comma)
735705
elements.append(
736706
.labeledSpecializeEntry(
737707
RawLabeledSpecializeEntrySyntax(
738-
label: ident,
708+
unexpectedBeforeLabel,
709+
label: label,
739710
unexpectedBeforeColon,
740711
colon: colon,
741712
value: valueLabel,
@@ -746,14 +717,15 @@ extension Parser {
746717
)
747718
case (.spiModule, let handle)?,
748719
(.spi, let handle)?:
749-
let ident = self.eat(handle)
720+
let (unexpectedBeforeLabel, label) = self.eat(handle)
750721
let (unexpectedBeforeColon, colon) = self.expect(.colon)
751722
let valueLabel = self.consumeAnyToken()
752723
let comma = self.consume(if: .comma)
753724
elements.append(
754725
.labeledSpecializeEntry(
755726
RawLabeledSpecializeEntrySyntax(
756-
label: ident,
727+
unexpectedBeforeLabel,
728+
label: label,
757729
unexpectedBeforeColon,
758730
colon: colon,
759731
value: valueLabel,
@@ -770,7 +742,8 @@ extension Parser {
770742
elements.append(
771743
.labeledSpecializeEntry(
772744
RawLabeledSpecializeEntrySyntax(
773-
label: ident,
745+
RawUnexpectedNodesSyntax([ident], arena: self.arena),
746+
label: RawTokenSyntax(missing: .identifier, arena: self.arena),
774747
unexpectedBeforeColon,
775748
colon: colon,
776749
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)