Skip to content

Commit 489a750

Browse files
committed
Add diagnostic for wrong LabeledSpecializeEntry label
1 parent 31fd3dc commit 489a750

File tree

4 files changed

+81
-69
lines changed

4 files changed

+81
-69
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 15 additions & 64 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 parseSpecializeAttributeArgumentList() -> RawSpecializeAttributeArgumentListSyntax {
676642
var elements = [RawSpecializeAttributeArgumentListSyntax.Element]()
677643
// Parse optional "exported" and "kind" labeled parameters.
678644
var loopProgress = LoopProgressCondition()
679-
while !self.at(.endOfFile, .rightParen, .keyword(.where)) && self.hasProgressed(&loopProgress) {
680-
switch self.at(anyIn: SpecializeParameter.self) {
645+
LOOP: while !self.at(.endOfFile, .rightParen, .keyword(.where)) && self.hasProgressed(&loopProgress) {
646+
switch self.at(anyIn: LabeledSpecializeArgumentSyntax.LabelOptions.self) {
681647
case (.target, let handle)?:
682-
let ident = self.eat(handle)
648+
let 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,7 @@ extension Parser {
691657
elements.append(
692658
.specializeTargetFunctionArgument(
693659
RawSpecializeTargetFunctionArgumentSyntax(
694-
targetLabel: ident,
660+
targetLabel: label,
695661
unexpectedBeforeColon,
696662
colon: colon,
697663
declName: declName,
@@ -701,14 +667,14 @@ extension Parser {
701667
)
702668
)
703669
case (.availability, let handle)?:
704-
let ident = self.eat(handle)
670+
let label = self.eat(handle)
705671
let (unexpectedBeforeColon, colon) = self.expect(.colon)
706672
let availability = self.parseAvailabilitySpecList()
707673
let (unexpectedBeforeSemi, semi) = self.expect(.semicolon)
708674
elements.append(
709675
.specializeAvailabilityArgument(
710676
RawSpecializeAvailabilityArgumentSyntax(
711-
availabilityLabel: ident,
677+
availabilityLabel: label,
712678
unexpectedBeforeColon,
713679
colon: colon,
714680
availabilityArguments: availability,
@@ -719,7 +685,7 @@ extension Parser {
719685
)
720686
)
721687
case (.available, let handle)?:
722-
let ident = self.eat(handle)
688+
let label = self.eat(handle)
723689
let (unexpectedBeforeColon, colon) = self.expect(.colon)
724690
// FIXME: I have no idea what this is supposed to be, but the Syntax
725691
// tree only allows us to insert a token so we'll take anything.
@@ -728,7 +694,7 @@ extension Parser {
728694
elements.append(
729695
.labeledSpecializeArgument(
730696
RawLabeledSpecializeArgumentSyntax(
731-
label: ident,
697+
label: label,
732698
unexpectedBeforeColon,
733699
colon: colon,
734700
value: available,
@@ -738,14 +704,14 @@ extension Parser {
738704
)
739705
)
740706
case (.exported, let handle)?:
741-
let ident = self.eat(handle)
707+
let label = self.eat(handle)
742708
let (unexpectedBeforeColon, colon) = self.expect(.colon)
743709
let (unexpectedBeforeValue, value) = self.expect(.keyword(.true), .keyword(.false), default: .keyword(.false))
744710
let comma = self.consume(if: .comma)
745711
elements.append(
746712
.labeledSpecializeArgument(
747713
RawLabeledSpecializeArgumentSyntax(
748-
label: ident,
714+
label: label,
749715
unexpectedBeforeColon,
750716
colon: colon,
751717
unexpectedBeforeValue,
@@ -756,14 +722,14 @@ extension Parser {
756722
)
757723
)
758724
case (.kind, let handle)?:
759-
let ident = self.eat(handle)
725+
let label = self.eat(handle)
760726
let (unexpectedBeforeColon, colon) = self.expect(.colon)
761727
let valueLabel = self.parseAnyIdentifier()
762728
let comma = self.consume(if: .comma)
763729
elements.append(
764730
.labeledSpecializeArgument(
765731
RawLabeledSpecializeArgumentSyntax(
766-
label: ident,
732+
label: label,
767733
unexpectedBeforeColon,
768734
colon: colon,
769735
value: valueLabel,
@@ -774,14 +740,14 @@ extension Parser {
774740
)
775741
case (.spiModule, let handle)?,
776742
(.spi, let handle)?:
777-
let ident = self.eat(handle)
743+
let label = self.eat(handle)
778744
let (unexpectedBeforeColon, colon) = self.expect(.colon)
779745
let valueLabel = self.consumeAnyToken()
780746
let comma = self.consume(if: .comma)
781747
elements.append(
782748
.labeledSpecializeArgument(
783749
RawLabeledSpecializeArgumentSyntax(
784-
label: ident,
750+
label: label,
785751
unexpectedBeforeColon,
786752
colon: colon,
787753
value: valueLabel,
@@ -791,22 +757,7 @@ extension Parser {
791757
)
792758
)
793759
case nil:
794-
let ident = self.consumeAnyToken()
795-
let (unexpectedBeforeColon, colon) = self.expect(.colon)
796-
let valueLabel = self.consumeAnyToken()
797-
let comma = self.consume(if: .comma)
798-
elements.append(
799-
.labeledSpecializeArgument(
800-
RawLabeledSpecializeArgumentSyntax(
801-
label: ident,
802-
unexpectedBeforeColon,
803-
colon: colon,
804-
value: valueLabel,
805-
trailingComma: comma,
806-
arena: self.arena
807-
)
808-
)
809-
)
760+
break LOOP
810761
}
811762
}
812763

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: LabeledSpecializeArgumentSyntax) -> 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: LabeledSpecializeArgumentSyntax.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
@@ -524,6 +524,23 @@ public struct UnknownDirectiveError: ParserError {
524524
}
525525
}
526526

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

529546
public struct EffectSpecifierDeclaredHere: ParserNote {

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,27 @@ 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
),
@@ -99,12 +107,29 @@ final class AttributeTests: XCTestCase {
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: """
110135
@_specialize(e:, exported: false) <#declaration#>

0 commit comments

Comments
 (0)