Skip to content

Merge missing token diagnostics if they occur at the same source location #838

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
merged 1 commit into from
Sep 27, 2022

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Sep 23, 2022

No description provided.

@ahoppen ahoppen force-pushed the ahoppen/merge-diags branch from 9a20d2f to 3ad5464 Compare September 23, 2022 18:12
fixIts = []
}

// Find the previous sibling, skipping over siblings that don't actually contain any source text.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use .sourceAccurate here rather than checking for missing?

Rather than grabbing previous siblings and replacing previous diagnostics, could we instead grab next siblings and just create the one diagnostic at this point? That would avoid looking through all diagnostics each time. IMO we also want to merge the fix-its, which that change would also make easier.

We may want to start splitting these files up as well. Ie. the generation + message for this one could be defined in a MissingSyntaxDiagnostic.swift instead (it's a fair amount of code).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use .sourceAccurate here rather than checking for missing?

No, because then we wouldn’t find missing tokens, which are skipped in the .sourceAccurate traversal mode.

Rather than grabbing previous siblings and replacing previous diagnostics, could we instead grab next siblings and just create the one diagnostic at this point? That would avoid looking through all diagnostics each time. IMO we also want to merge the fix-its, which that change would also make easier.

Good idea. I thought that this would be more difficult than looking behind but it turns out that it’s actually simpler.

We may want to start splitting these files up as well. Ie. the generation + message for this one could be defined in a MissingSyntaxDiagnostic.swift instead (it's a fair amount of code).

Good idea.

}
private extension String {
/// Remove any leading or trailing whitespace
func trimmingWhitespaces() -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoiding a Foundation import I assume? Crazy that we have drop and trimPrefix but not a trimSuffix even though drop and trimPrefix are basically equivalent. Also note that this isn't "whitespace" but rather "spaces".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly. I wished we had functionality like this in the stdlib but apparently we don’t 😬

Good catch with spaces vs whitespace

init(missingNodes: [Syntax]) {
assert(!missingNodes.isEmpty)
self.missingNodes = missingNodes
self.commonParent = missingNodes.first!.parent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the spirit of not-crashing IMO it would be nice to avoid ! here and just return some generic message if they're empty.

let (rawKind, text) = missingToken.tokenKind.decomposeToRaw()
if let text = text, !text.isEmpty {
let presentToken = TokenSyntax(missingToken.tokenKind, presence: .present)
newPart = .sourceText("\(Syntax(Format().format(syntax: presentToken)))")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have an API where you don't have to instantiate Format first. IMO this could even just be on the syntax node's themselves (ie. presentToken.formatted()).


var description: String {
switch self {
case .sourceText(let content):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could take a TokenSyntax instead. Both uses pass the same "\(Syntax(Format().format(syntax: presentToken)))".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can’t take a TokenSyntax because we are merging these further below. But [TokenSyntax] is possible. I changed it to that.

switch missingToken.tokenKind {
case .rightBrace, .rightAngle, .rightParen, .rightSquareBracket:
if commonParent.children(viewMode: .fixedUp).last?.as(TokenSyntax.self) == missingToken {
if nodePosition == .toStart {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in is the default, just if nodePosition != .toStart?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored this logic quite a bit to make it simpler so it doesn’t use ContextType anymore.

}
}
return message
if let commonParent = commonParent, let parentTypeName = commonParent.nodeTypeNameForDiagnostics(allowSourceFile: false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this check with the guard above (and then all the commonParent checks as well)

public let attributeName: TokenSyntax

public var message: String {
return "Expected argument for '@\(attributeName)' attribute"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argument or arguments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think based on the way we currently model attributes we don’t really know whether a particular attribute requires a single argument or multiple… Might be something to tackle in a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argument(s) 😅

Comment on lines 227 to 233
let nextSiblingIndex = siblings.index(after: self.index)
if let previousAbsoluteRawSibling = siblings[nextSiblingIndex...].last {
return Syntax(SyntaxData(previousAbsoluteRawSibling, parent: parent))
} else {
return nil
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth adding some quick tests for these - this should be .first.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After making some changes, it turns out that we don’t need previousSibling and nextSibling anymore.

Comment on lines +315 to 316
DiagnosticSpec(message: "Expected 'set)' to end modifier"),
DiagnosticSpec(message: "Unexpected text 'get, didSet' in variable")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth specializing this one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you expect there?

Copy link
Contributor

@bnbarham bnbarham Sep 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally 'private' only accepts 'set' with a replacement for get, didSet -> set and then a Expected ')' to end modifier. And I'd only expect that if the unexpected was one of get/didSet/willSet.

This is fine for now, could add an issue for it though if you agree.

DiagnosticSpec(locationMarker: "EXPECTED_DECL", message: "Expected declaration after '{' in struct"),
DiagnosticSpec(locationMarker: "EXPECTED_DECL", message: "Expected declaration in struct"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I agree with doing this, especially since the location would be the "after" anyway (though I suppose not all IDEs display the actual column).

@@ -395,8 +395,8 @@ final class ExpressionTests: XCTestCase {
\#^AFTER_SLASH^#\(#^AFTER_PAREN^#
"""##,
diagnostics: [
DiagnosticSpec(locationMarker: "AFTER_SLASH", message: #"Expected root and expression after '\' in key path"#),
DiagnosticSpec(locationMarker: "AFTER_PAREN", message: "Expected value after '(' in tuple"),
DiagnosticSpec(locationMarker: "AFTER_SLASH", message: #"Expected root and expression in key path"#),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue up for key paths in general? root isn't required, but it looks like we're calling parsePostfixExpression if there's no ., that returns missing, and thus we get an expected "root" here where that makes no sense (the type isn't required, really I'd expect "Expected '.' and expression in key path" here).

@@ -467,7 +467,7 @@ final class ExpressionTests: XCTestCase {
diagnostics: [
DiagnosticSpec(message: "Expected identifier in '#keyPath' expression"),
DiagnosticSpec(message: "Expected ')' to end '#keyPath' expression"),
DiagnosticSpec(locationMarker: "MISSING_VALUE", message: "Expected value after ':' in function call"),
DiagnosticSpec(locationMarker: "MISSING_VALUE", message: "Expected value in function call"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this would be "argument" but that's not particularly easy with the current scheme 🤔.

@@ -476,7 +476,7 @@ final class ExpressionTests: XCTestCase {
"[(Int) -> #^DIAG^#throws Int]()",
diagnostics: [
// FIXME: We should suggest to move 'throws' in front of '->'
DiagnosticSpec(message: "Expected expression after '->' in array element"),
DiagnosticSpec(message: "Expected expression in array element"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignoring the throws problem, this not being something like "expected return type" is also an issue.

DiagnosticSpec(locationMarker: "DIAG_1", message: "Unexpected text '..' in function type"),
DiagnosticSpec(locationMarker: "END", message: "Expected type after '->' in function type"),
DiagnosticSpec(locationMarker: "END", message: "Expected type in function type"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the above, without after '->' this would really need to be expected return type.

@ahoppen ahoppen force-pushed the ahoppen/merge-diags branch 2 times, most recently from 106d379 to d4bab52 Compare September 26, 2022 13:29
Comment on lines +315 to 316
DiagnosticSpec(message: "Expected 'set)' to end modifier"),
DiagnosticSpec(message: "Unexpected text 'get, didSet' in variable")
Copy link
Contributor

@bnbarham bnbarham Sep 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally 'private' only accepts 'set' with a replacement for get, didSet -> set and then a Expected ')' to end modifier. And I'd only expect that if the unexpected was one of get/didSet/willSet.

This is fine for now, could add an issue for it though if you agree.

public let attributeName: TokenSyntax

public var message: String {
return "Expected argument for '@\(attributeName)' attribute"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argument(s) 😅

@@ -6,16 +6,19 @@ final class AttributeTests: XCTestCase {
func testMissingArgumentToAttribute() {
AssertParse(
"""
@_dynamicReplacement(#^DIAG_1^#
@_dynamicReplacement(#^DIAG^#
func #^DIAG_2^#test_dynamic_replacement_for2() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove DIAG_2

DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected ')' to end attribute", fixIts: ["Insert ')'"]),
],
fixedSource: """
@differentiable(reverse wrt: <#syntax#>,where T = <#type#>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be 'parameters'? Not much better than 'syntax` though 🤔

DiagnosticSpec(locationMarker: "DIAG_2", message: "Expected member block in class"),
],
fixedSource: """
}class C {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be removing the first }?

@@ -0,0 +1,38 @@
//===--- DiagnosticExtensions.swift ---------------------------------------===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add all the new files to CMakeLists.txt?

@ahoppen ahoppen force-pushed the ahoppen/merge-diags branch from d4bab52 to 1289d74 Compare September 27, 2022 07:25
@ahoppen ahoppen marked this pull request as ready for review September 27, 2022 07:25
@ahoppen
Copy link
Member Author

ahoppen commented Sep 27, 2022

@swift-ci Please test

@ahoppen ahoppen merged commit 8a4fa32 into swiftlang:main Sep 27, 2022
@ahoppen ahoppen deleted the ahoppen/merge-diags branch September 27, 2022 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants