Skip to content

Commit 325f869

Browse files
committed
Fixed testAvailabilityQueryUnavailability34a
1 parent ced4443 commit 325f869

File tree

5 files changed

+105
-19
lines changed

5 files changed

+105
-19
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()
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/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
@@ -237,6 +237,15 @@ public struct AsyncMustPrecedeThrows: ParserError {
237237
}
238238
}
239239

240+
public struct AvailabilityConditionAsExpression: ParserError {
241+
public let availabilityToken: TokenSyntax
242+
public let negatedAvailabilityToken: TokenSyntax
243+
244+
public var message: String {
245+
return "\(availabilityToken) cannot be used as an expression, did you mean to use '\(negatedAvailabilityToken)'?"
246+
}
247+
}
248+
240249
public struct AvailabilityConditionInExpression: ParserError {
241250
public let availabilityCondition: AvailabilityConditionSyntax
242251

@@ -596,9 +605,9 @@ public struct RemoveNodesFixIt: ParserFixIt {
596605
public struct ReplaceTokensFixIt: ParserFixIt {
597606
public let replaceTokens: [TokenSyntax]
598607

599-
public let replacement: TokenSyntax
608+
public let replacements: [TokenSyntax]
600609

601610
public var message: String {
602-
"replace \(nodesDescription(replaceTokens, format: false)) with '\(replacement.text)'"
611+
"replace \(nodesDescription(replaceTokens, format: false)) with \(nodesDescription(replacements, format: false))"
603612
}
604613
}

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)