Skip to content

Commit f47c7b2

Browse files
committed
Fixed testAvailabilityQueryUnavailability34a
1 parent d8dee5c commit f47c7b2

File tree

6 files changed

+110
-21
lines changed

6 files changed

+110
-21
lines changed

Sources/SwiftParser/Statements.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,14 @@ extension Parser {
187187
var loopProgress = LoopProgressCondition()
188188
repeat {
189189
let condition = self.parseConditionElement(lastBindingKind: elements.last?.condition.as(RawOptionalBindingConditionSyntax.self)?.bindingKeyword)
190-
let unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax?
190+
var unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax? = nil
191+
if let equalOperator = self.consumeIfContextualPunctuator("=="), let falseKeyword = self.consume(if: .keyword(.false)) {
192+
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax([equalOperator, falseKeyword], arena: self.arena)
193+
}
191194
keepGoing = self.consume(if: .comma)
192195
if keepGoing == nil, let andOperator = self.consumeIfContextualPunctuator("&&") {
193-
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax([andOperator], arena: self.arena)
196+
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax(combining: unexpectedBeforeKeepGoing, andOperator, arena: self.arena)
194197
keepGoing = missingToken(.comma)
195-
} else {
196-
unexpectedBeforeKeepGoing = nil
197198
}
198199
elements.append(
199200
RawConditionElementSyntax(

Sources/SwiftParserDiagnostics/MissingNodesError.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,16 @@ func nodesDescription<SyntaxType: SyntaxProtocol>(_ nodes: [SyntaxType], format:
112112
func nodesDescriptionAndCommonParent<SyntaxType: SyntaxProtocol>(_ nodes: [SyntaxType], format: Bool) -> (commonAncestor: Syntax?, description: String) {
113113
let missingSyntaxNodes = nodes.map(Syntax.init)
114114

115-
// If all tokens in the parent are missing, return the parent type name.
115+
// If all tokens in the parent are missing, return the parent type name unless
116+
// we are replacing by a single token that has explicit text, in which case we
117+
// return that explicit text.
116118
if let commonAncestor = findCommonAncestor(missingSyntaxNodes),
117119
commonAncestor.isMissingAllTokens,
118120
let firstToken = commonAncestor.firstToken(viewMode: .all),
119121
let lastToken = commonAncestor.lastToken(viewMode: .all),
120122
missingSyntaxNodes.contains(Syntax(firstToken)),
121-
missingSyntaxNodes.contains(Syntax(lastToken))
123+
missingSyntaxNodes.contains(Syntax(lastToken)),
124+
nodes.only?.as(TokenSyntax.self)?.text != nil
122125
{
123126
if let nodeTypeName = commonAncestor.nodeTypeNameForDiagnostics(allowBlockNames: true) {
124127
return (commonAncestor, nodeTypeName)

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ import SwiftDiagnostics
1414
import SwiftParser
1515
@_spi(RawSyntax) import SwiftSyntax
1616

17+
fileprivate func getTokens(between first: TokenSyntax, and second: TokenSyntax) -> [TokenSyntax] {
18+
var tokens: [TokenSyntax] = []
19+
var currentToken = first
20+
21+
while currentToken != second {
22+
tokens.append(currentToken)
23+
guard let nextToken = currentToken.nextToken(viewMode: .sourceAccurate) else {
24+
assertionFailure("second Token must occur after first Token")
25+
return tokens
26+
}
27+
currentToken = nextToken
28+
}
29+
tokens.append(second)
30+
return tokens
31+
}
32+
1733
fileprivate extension TokenSyntax {
1834
/// Assuming this token is a `poundAvailableKeyword` or `poundUnavailableKeyword`
1935
/// returns the opposite keyword.
@@ -239,7 +255,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
239255
unexpectedTokenCondition: isOfSameKind,
240256
correctTokens: [specifier],
241257
message: { _ in misspelledError },
242-
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: specifier) },
258+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [specifier]) },
243259
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
244260
)
245261
}
@@ -434,7 +450,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
434450
unexpectedTokenCondition: { $0.text == "||" },
435451
correctTokens: [node.trailingComma],
436452
message: { _ in .joinPlatformsUsingComma },
437-
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: trailingComma) }
453+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [trailingComma]) }
438454
)
439455
}
440456
return .visitChildren
@@ -463,13 +479,42 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
463479
if shouldSkip(node) {
464480
return .skipChildren
465481
}
482+
if let unexpected = node.unexpectedBetweenConditionAndTrailingComma,
483+
let availability = node.condition.as(AvailabilityConditionSyntax.self),
484+
let (_, falseKeyword) = unexpected.twoTokens(
485+
firstSatisfying: { $0.tokenKind == .binaryOperator("==") },
486+
secondSatisfying: { $0.tokenKind == .keyword(.false) }
487+
)
488+
{
489+
// Diagnose #available used as an expression
490+
let negatedAvailabilityKeyword = availability.availabilityKeyword.negatedAvailabilityKeyword
491+
let negatedCoditionElement = ConditionElementSyntax(
492+
condition: .availability(availability.with(\.availabilityKeyword, negatedAvailabilityKeyword)),
493+
trailingComma: node.trailingComma
494+
)
495+
if let negatedAvailability = negatedCoditionElement.condition.as(AvailabilityConditionSyntax.self) {
496+
addDiagnostic(
497+
unexpected,
498+
AvailabilityConditionAsExpression(availabilityToken: availability.availabilityKeyword, negatedAvailabilityToken: negatedAvailabilityKeyword),
499+
fixIts: [
500+
FixIt(
501+
message: ReplaceTokensFixIt(replaceTokens: getTokens(between: availability.availabilityKeyword, and: falseKeyword), replacements: getTokens(between: negatedAvailability.availabilityKeyword, and: negatedAvailability.rightParen)),
502+
changes: [
503+
.replace(oldNode: Syntax(node), newNode: Syntax(negatedCoditionElement))
504+
]
505+
)
506+
],
507+
handledNodes: [unexpected.id]
508+
)
509+
}
510+
}
466511
if let trailingComma = node.trailingComma {
467512
exchangeTokens(
468513
unexpected: node.unexpectedBetweenConditionAndTrailingComma,
469514
unexpectedTokenCondition: { $0.text == "&&" },
470515
correctTokens: [node.trailingComma],
471516
message: { _ in .joinConditionsUsingComma },
472-
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: trailingComma) }
517+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [trailingComma]) }
473518
)
474519
}
475520
return .visitChildren
@@ -662,7 +707,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
662707
.typeParameterPackEllipsis,
663708
fixIts: [
664709
FixIt(
665-
message: ReplaceTokensFixIt(replaceTokens: [unexpectedEllipsis], replacement: .keyword(.each)),
710+
message: ReplaceTokensFixIt(replaceTokens: [unexpectedEllipsis], replacements: [.keyword(.each)]),
666711
changes: [
667712
.makeMissing(unexpected),
668713
.makePresent(each, trailingTrivia: .space),
@@ -678,7 +723,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
678723
unexpectedTokenCondition: { $0.tokenKind == .keyword(.class) },
679724
correctTokens: [inheritedTypeName],
680725
message: { _ in StaticParserError.classConstraintCanOnlyBeUsedInProtocol },
681-
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: inheritedTypeName) }
726+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [inheritedTypeName]) }
682727
)
683728
}
684729
return .visitChildren
@@ -709,7 +754,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
709754
NegatedAvailabilityCondition(avaialabilityCondition: availability, negatedAvailabilityKeyword: negatedAvailabilityKeyword),
710755
fixIts: [
711756
FixIt(
712-
message: ReplaceTokensFixIt(replaceTokens: [operatorToken, availability.availabilityKeyword], replacement: negatedAvailabilityKeyword),
757+
message: ReplaceTokensFixIt(replaceTokens: [operatorToken, availability.availabilityKeyword], replacements: [negatedAvailabilityKeyword]),
713758
changes: [
714759
.replace(oldNode: Syntax(conditionElement), newNode: Syntax(negatedCoditionElement))
715760
]
@@ -741,7 +786,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
741786
StaticParserError.unexpectedPoundElseSpaceIf,
742787
fixIts: [
743788
FixIt(
744-
message: ReplaceTokensFixIt(replaceTokens: unexpectedTokens, replacement: clause.poundKeyword),
789+
message: ReplaceTokensFixIt(replaceTokens: unexpectedTokens, replacements: [clause.poundKeyword]),
745790
changes: [
746791
.makeMissing(unexpectedBeforePoundKeyword, transferTrivia: false),
747792
.makePresent(clause.poundKeyword, leadingTrivia: unexpectedBeforePoundKeyword.leadingTrivia),
@@ -785,7 +830,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
785830
.expectedAssignmentInsteadOfComparisonOperator,
786831
fixIts: [
787832
FixIt(
788-
message: ReplaceTokensFixIt(replaceTokens: [.binaryOperator("==")], replacement: node.equal),
833+
message: ReplaceTokensFixIt(replaceTokens: [.binaryOperator("==")], replacements: [node.equal]),
789834
changes: [.makeMissing(unexpected), .makePresent(node.equal, leadingTrivia: [])]
790835
)
791836
],
@@ -799,7 +844,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
799844
unexpectedTokenCondition: { $0.tokenKind == .colon },
800845
correctTokens: [node.equal],
801846
message: { _ in StaticParserError.initializerInPattern },
802-
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: node.equal) }
847+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [node.equal]) }
803848
)
804849
}
805850
return .visitChildren
@@ -1005,7 +1050,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
10051050
}
10061051
if let singleQuote = node.unexpectedBetweenOpenDelimiterAndOpenQuote?.onlyToken(where: { $0.tokenKind == .singleQuote }) {
10071052
let fixIt = FixIt(
1008-
message: ReplaceTokensFixIt(replaceTokens: [singleQuote], replacement: node.openQuote),
1053+
message: ReplaceTokensFixIt(replaceTokens: [singleQuote], replacements: [node.openQuote]),
10091054
changes: [
10101055
.makeMissing(singleQuote, transferTrivia: false),
10111056
.makePresent(node.openQuote, leadingTrivia: singleQuote.leadingTrivia),
@@ -1181,7 +1226,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11811226
unexpectedTokenCondition: { $0.tokenKind == .colon },
11821227
correctTokens: [node.equal],
11831228
message: { _ in MissingNodesError(missingNodes: [Syntax(node.equal)]) },
1184-
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: node.equal) }
1229+
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [node.equal]) }
11851230
)
11861231
}
11871232
return .visitChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ public struct AsyncMustPrecedeThrows: ParserError {
240240
}
241241
}
242242

243+
public struct AvailabilityConditionAsExpression: ParserError {
244+
public let availabilityToken: TokenSyntax
245+
public let negatedAvailabilityToken: TokenSyntax
246+
247+
public var message: String {
248+
return "\(availabilityToken) cannot be used as an expression, did you mean to use '\(negatedAvailabilityToken)'?"
249+
}
250+
}
251+
243252
public struct AvailabilityConditionInExpression: ParserError {
244253
public let availabilityCondition: AvailabilityConditionSyntax
245254

@@ -602,9 +611,9 @@ public struct RemoveNodesFixIt: ParserFixIt {
602611
public struct ReplaceTokensFixIt: ParserFixIt {
603612
public let replaceTokens: [TokenSyntax]
604613

605-
public let replacement: TokenSyntax
614+
public let replacements: [TokenSyntax]
606615

607616
public var message: String {
608-
"replace \(nodesDescription(replaceTokens, format: false)) with '\(replacement.text)'"
617+
"replace \(nodesDescription(replaceTokens, format: false)) with \(nodesDescription(replacements, format: false))"
609618
}
610619
}

Sources/SwiftParserDiagnostics/SyntaxExtensions.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ extension UnexpectedNodesSyntax {
3939
return nil
4040
}
4141
}
42+
43+
/// If this only contains two tokens, the first satisfying `firstCondition`, and the second satisfying `secondCondition`,
44+
/// return these tokens as a tuple, otherwise return `nil`.
45+
func twoTokens(
46+
firstSatisfying firstCondition: (TokenSyntax) -> Bool,
47+
secondSatisfying secondCondition: (TokenSyntax) -> Bool
48+
) -> (first: TokenSyntax, second: TokenSyntax)? {
49+
let sourceAccurateChildren = self.children(viewMode: .sourceAccurate).compactMap({ $0.as(TokenSyntax.self) })
50+
guard sourceAccurateChildren.count == 2 else {
51+
return nil
52+
}
53+
guard firstCondition(sourceAccurateChildren[0]) && secondCondition(sourceAccurateChildren[1]) else {
54+
return nil
55+
}
56+
return (sourceAccurateChildren[0], sourceAccurateChildren[1])
57+
}
4258
}
4359

4460
extension Syntax {

Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,13 +441,28 @@ final class AvailabilityQueryUnavailabilityTests: XCTestCase {
441441
}
442442
""",
443443
diagnostics: [
444-
// TODO: (good first issue) Old parser expected error on line 2: #available cannot be used as an expression, did you mean to use '#unavailable'?, Fix-It replacements: 4 - 14 = '#unavailable', 18 - 27 = ''
445-
DiagnosticSpec(message: "unexpected code '== false' in 'if' statement")
444+
DiagnosticSpec(message: "#available cannot be used as an expression, did you mean to use '#unavailable'?", fixIts: ["replace '#available(*) == false' with '#unavailable(*)'"])
446445
]
447446
)
448447
}
449448

450449
func testAvailabilityQueryUnavailability34b() {
450+
assertParse(
451+
"""
452+
// Diagnose wrong spellings of unavailability
453+
if #available(*) 1️⃣== false && 2️⃣true {
454+
}
455+
""",
456+
diagnostics: [
457+
DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '== false &&' in 'if' statement"),
458+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ',' in 'if' statement", fixIts: ["insert ','"]),
459+
// TODO: Old parser expected error on line 2: #available cannot be used as an expression, did you mean to use '#unavailable'?, Fix-It replacements: 4 - 14 = '#unavailable', 18 - 27 = ''
460+
// TODO: Old parser expected error on line 2: expected ',' joining parts of a multi-clause condition, Fix-It replacements: 27 - 28 = ','
461+
]
462+
)
463+
}
464+
465+
func testAvailabilityQueryUnavailability34c() {
451466
assertParse(
452467
"""
453468
if !1️⃣#available(*) {

0 commit comments

Comments
 (0)