Skip to content

Fixed testAvailabilityQueryUnavailability34a #1464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension TokenConsumer {
} else {
return true
}
case (.primaryExpressionStart(.atSign), let handle)?:
case (.primaryExpressionStart(.atSign), _)?:
break
case (_, _)?:
return true
Expand Down
9 changes: 5 additions & 4 deletions Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,14 @@ extension Parser {
var loopProgress = LoopProgressCondition()
repeat {
let condition = self.parseConditionElement(lastBindingKind: elements.last?.condition.as(RawOptionalBindingConditionSyntax.self)?.bindingKeyword)
let unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax?
var unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax? = nil
if let equalOperator = self.consumeIfContextualPunctuator("=="), let falseKeyword = self.consume(if: .keyword(.false)) {
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax([equalOperator, falseKeyword], arena: self.arena)
}
keepGoing = self.consume(if: .comma)
if keepGoing == nil, let andOperator = self.consumeIfContextualPunctuator("&&") {
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax([andOperator], arena: self.arena)
unexpectedBeforeKeepGoing = RawUnexpectedNodesSyntax(combining: unexpectedBeforeKeepGoing, andOperator, arena: self.arena)
keepGoing = missingToken(.comma)
} else {
unexpectedBeforeKeepGoing = nil
}
elements.append(
RawConditionElementSyntax(
Expand Down
14 changes: 12 additions & 2 deletions Sources/SwiftParserDiagnostics/MissingNodesError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,23 @@ func nodesDescription<SyntaxType: SyntaxProtocol>(_ nodes: [SyntaxType], format:
func nodesDescriptionAndCommonParent<SyntaxType: SyntaxProtocol>(_ nodes: [SyntaxType], format: Bool) -> (commonAncestor: Syntax?, description: String) {
let missingSyntaxNodes = nodes.map(Syntax.init)

// If all tokens in the parent are missing, return the parent type name.
let isOnlyTokenWithNonMissingText: Bool
if let token = nodes.only?.as(TokenSyntax.self) {
isOnlyTokenWithNonMissingText = token.text != ""
} else {
isOnlyTokenWithNonMissingText = false
}

// If all tokens in the parent are missing, return the parent type name unless
// we are replacing by a single token that has explicit text, in which case we
// return that explicit text.
if let commonAncestor = findCommonAncestor(missingSyntaxNodes),
commonAncestor.isMissingAllTokens,
let firstToken = commonAncestor.firstToken(viewMode: .all),
let lastToken = commonAncestor.lastToken(viewMode: .all),
missingSyntaxNodes.contains(Syntax(firstToken)),
missingSyntaxNodes.contains(Syntax(lastToken))
missingSyntaxNodes.contains(Syntax(lastToken)),
!isOnlyTokenWithNonMissingText
{
if let nodeTypeName = commonAncestor.nodeTypeNameForDiagnostics(allowBlockNames: true) {
return (commonAncestor, nodeTypeName)
Expand Down
69 changes: 57 additions & 12 deletions Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ import SwiftDiagnostics
import SwiftParser
@_spi(RawSyntax) import SwiftSyntax

fileprivate func getTokens(between first: TokenSyntax, and second: TokenSyntax) -> [TokenSyntax] {
var tokens: [TokenSyntax] = []
var currentToken = first

while currentToken != second {
tokens.append(currentToken)
guard let nextToken = currentToken.nextToken(viewMode: .sourceAccurate) else {
assertionFailure("second Token must occur after first Token")
return tokens
}
currentToken = nextToken
}
tokens.append(second)
return tokens
}

fileprivate extension TokenSyntax {
/// Assuming this token is a `poundAvailableKeyword` or `poundUnavailableKeyword`
/// returns the opposite keyword.
Expand Down Expand Up @@ -239,7 +255,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
unexpectedTokenCondition: isOfSameKind,
correctTokens: [specifier],
message: { _ in misspelledError },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: specifier) },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [specifier]) },
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
)
}
Expand Down Expand Up @@ -438,7 +454,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
unexpectedTokenCondition: { $0.text == "||" },
correctTokens: [node.trailingComma],
message: { _ in .joinPlatformsUsingComma },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: trailingComma) }
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [trailingComma]) }
)
}
return .visitChildren
Expand Down Expand Up @@ -467,13 +483,42 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
if shouldSkip(node) {
return .skipChildren
}
if let unexpected = node.unexpectedBetweenConditionAndTrailingComma,
let availability = node.condition.as(AvailabilityConditionSyntax.self),
let (_, falseKeyword) = unexpected.twoTokens(
firstSatisfying: { $0.tokenKind == .binaryOperator("==") },
secondSatisfying: { $0.tokenKind == .keyword(.false) }
)
{
// Diagnose #available used as an expression
let negatedAvailabilityKeyword = availability.availabilityKeyword.negatedAvailabilityKeyword
let negatedCoditionElement = ConditionElementSyntax(
condition: .availability(availability.with(\.availabilityKeyword, negatedAvailabilityKeyword)),
trailingComma: node.trailingComma
)
if let negatedAvailability = negatedCoditionElement.condition.as(AvailabilityConditionSyntax.self) {
addDiagnostic(
unexpected,
AvailabilityConditionAsExpression(availabilityToken: availability.availabilityKeyword, negatedAvailabilityToken: negatedAvailabilityKeyword),
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: getTokens(between: availability.availabilityKeyword, and: falseKeyword), replacements: getTokens(between: negatedAvailability.availabilityKeyword, and: negatedAvailability.rightParen)),
changes: [
.replace(oldNode: Syntax(node), newNode: Syntax(negatedCoditionElement))
]
)
],
handledNodes: [unexpected.id]
)
}
}
if let trailingComma = node.trailingComma {
exchangeTokens(
unexpected: node.unexpectedBetweenConditionAndTrailingComma,
unexpectedTokenCondition: { $0.text == "&&" },
correctTokens: [node.trailingComma],
message: { _ in .joinConditionsUsingComma },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: trailingComma) }
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [trailingComma]) }
)
}
return .visitChildren
Expand Down Expand Up @@ -563,7 +608,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
.expectedCommaInWhereClause,
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: [token], replacement: .commaToken()),
message: ReplaceTokensFixIt(replaceTokens: [token], replacements: [.commaToken()]),
changes: [
.makeMissing(token),
.makePresent(trailingComma),
Expand Down Expand Up @@ -698,7 +743,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
.typeParameterPackEllipsis,
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: [unexpectedEllipsis], replacement: .keyword(.each)),
message: ReplaceTokensFixIt(replaceTokens: [unexpectedEllipsis], replacements: [.keyword(.each)]),
changes: [
.makeMissing(unexpected),
.makePresent(each, trailingTrivia: .space),
Expand All @@ -714,7 +759,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
unexpectedTokenCondition: { $0.tokenKind == .keyword(.class) },
correctTokens: [inheritedTypeName],
message: { _ in StaticParserError.classConstraintCanOnlyBeUsedInProtocol },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: inheritedTypeName) }
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [inheritedTypeName]) }
)
}
return .visitChildren
Expand Down Expand Up @@ -745,7 +790,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
NegatedAvailabilityCondition(avaialabilityCondition: availability, negatedAvailabilityKeyword: negatedAvailabilityKeyword),
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: [operatorToken, availability.availabilityKeyword], replacement: negatedAvailabilityKeyword),
message: ReplaceTokensFixIt(replaceTokens: [operatorToken, availability.availabilityKeyword], replacements: [negatedAvailabilityKeyword]),
changes: [
.replace(oldNode: Syntax(conditionElement), newNode: Syntax(negatedCoditionElement))
]
Expand Down Expand Up @@ -777,7 +822,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
StaticParserError.unexpectedPoundElseSpaceIf,
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: unexpectedTokens, replacement: clause.poundKeyword),
message: ReplaceTokensFixIt(replaceTokens: unexpectedTokens, replacements: [clause.poundKeyword]),
changes: [
.makeMissing(unexpectedBeforePoundKeyword, transferTrivia: false),
.makePresent(clause.poundKeyword, leadingTrivia: unexpectedBeforePoundKeyword.leadingTrivia),
Expand Down Expand Up @@ -821,7 +866,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
.expectedAssignmentInsteadOfComparisonOperator,
fixIts: [
FixIt(
message: ReplaceTokensFixIt(replaceTokens: [.binaryOperator("==")], replacement: node.equal),
message: ReplaceTokensFixIt(replaceTokens: [.binaryOperator("==")], replacements: [node.equal]),
changes: [.makeMissing(unexpected), .makePresent(node.equal, leadingTrivia: [])]
)
],
Expand All @@ -835,7 +880,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
unexpectedTokenCondition: { $0.tokenKind == .colon },
correctTokens: [node.equal],
message: { _ in StaticParserError.initializerInPattern },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: node.equal) }
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [node.equal]) }
)
}
return .visitChildren
Expand Down Expand Up @@ -1052,7 +1097,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
}
if let singleQuote = node.unexpectedBetweenOpenDelimiterAndOpenQuote?.onlyToken(where: { $0.tokenKind == .singleQuote }) {
let fixIt = FixIt(
message: ReplaceTokensFixIt(replaceTokens: [singleQuote], replacement: node.openQuote),
message: ReplaceTokensFixIt(replaceTokens: [singleQuote], replacements: [node.openQuote]),
changes: [
.makeMissing(singleQuote, transferTrivia: false),
.makePresent(node.openQuote, leadingTrivia: singleQuote.leadingTrivia),
Expand Down Expand Up @@ -1228,7 +1273,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
unexpectedTokenCondition: { $0.tokenKind == .colon },
correctTokens: [node.equal],
message: { _ in MissingNodesError(missingNodes: [Syntax(node.equal)]) },
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacement: node.equal) }
moveFixIt: { ReplaceTokensFixIt(replaceTokens: $0, replacements: [node.equal]) }
)
}
return .visitChildren
Expand Down
13 changes: 11 additions & 2 deletions Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,15 @@ public struct AsyncMustPrecedeThrows: ParserError {
}
}

public struct AvailabilityConditionAsExpression: ParserError {
public let availabilityToken: TokenSyntax
public let negatedAvailabilityToken: TokenSyntax

public var message: String {
return "\(availabilityToken) cannot be used as an expression, did you mean to use '\(negatedAvailabilityToken)'?"
}
}

public struct AvailabilityConditionInExpression: ParserError {
public let availabilityCondition: AvailabilityConditionSyntax

Expand Down Expand Up @@ -608,9 +617,9 @@ public struct RemoveNodesFixIt: ParserFixIt {
public struct ReplaceTokensFixIt: ParserFixIt {
public let replaceTokens: [TokenSyntax]

public let replacement: TokenSyntax
public let replacements: [TokenSyntax]

public var message: String {
"replace \(nodesDescription(replaceTokens, format: false)) with '\(replacement.text)'"
"replace \(nodesDescription(replaceTokens, format: false)) with \(nodesDescription(replacements, format: false))"
}
}
16 changes: 16 additions & 0 deletions Sources/SwiftParserDiagnostics/SyntaxExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ extension UnexpectedNodesSyntax {
return nil
}
}

/// If this only contains two tokens, the first satisfying `firstCondition`, and the second satisfying `secondCondition`,
/// return these tokens as a tuple, otherwise return `nil`.
func twoTokens(
firstSatisfying firstCondition: (TokenSyntax) -> Bool,
secondSatisfying secondCondition: (TokenSyntax) -> Bool
) -> (first: TokenSyntax, second: TokenSyntax)? {
let sourceAccurateChildren = self.children(viewMode: .sourceAccurate).compactMap({ $0.as(TokenSyntax.self) })
guard sourceAccurateChildren.count == 2 else {
return nil
}
guard firstCondition(sourceAccurateChildren[0]) && secondCondition(sourceAccurateChildren[1]) else {
return nil
}
return (sourceAccurateChildren[0], sourceAccurateChildren[1])
}
}

extension Syntax {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,28 @@ final class AvailabilityQueryUnavailabilityTests: XCTestCase {
}
""",
diagnostics: [
// 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 = ''
DiagnosticSpec(message: "unexpected code '== false' in 'if' statement")
DiagnosticSpec(message: "#available cannot be used as an expression, did you mean to use '#unavailable'?", fixIts: ["replace '#available(*) == false' with '#unavailable(*)'"])
]
)
}

func testAvailabilityQueryUnavailability34b() {
assertParse(
"""
// Diagnose wrong spellings of unavailability
if #available(*) 1️⃣== false && 2️⃣true {
}
""",
diagnostics: [
DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '== false &&' in 'if' statement"),
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ',' in 'if' statement", fixIts: ["insert ','"]),
// 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 = ''
// TODO: Old parser expected error on line 2: expected ',' joining parts of a multi-clause condition, Fix-It replacements: 27 - 28 = ','
]
)
}

func testAvailabilityQueryUnavailability34c() {
assertParse(
"""
if !1️⃣#available(*) {
Expand Down