Skip to content

When handling a missing layout node, only make the placeholder present #1894

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 4 commits into from
Jul 11, 2023
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
28 changes: 22 additions & 6 deletions CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public let COMMON_NODES: [Node] = [
nameForDiagnostics: "declaration",
documentation: "In case the source code is missing a declaration, this node stands in place of the missing declaration.",
traits: [
"MissingNode",
"WithAttributes",
"WithModifiers",
],
Expand All @@ -180,7 +181,7 @@ public let COMMON_NODES: [Node] = [
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
documentation: """
A placeholder, i.e. `<#decl#>` that can be inserted into the source code to represent the missing declaration.
A placeholder, i.e. `<#decl#>`, that can be inserted into the source code to represent the missing declaration.
This token should always have `presence = .missing`.
"""
),
Expand All @@ -192,12 +193,15 @@ public let COMMON_NODES: [Node] = [
base: .expr,
nameForDiagnostics: "expression",
documentation: "In case the source code is missing an expression, this node stands in place of the missing expression.",
traits: [
"MissingNode"
],
children: [
Child(
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
documentation: """
A placeholder, i.e. `<#expression#>` that can be inserted into the source code to represent the missing expression.
A placeholder, i.e. `<#expression#>`, that can be inserted into the source code to represent the missing expression.
This token should always have `presence = .missing`.
"""
)
Expand All @@ -209,12 +213,15 @@ public let COMMON_NODES: [Node] = [
base: .pattern,
nameForDiagnostics: "pattern",
documentation: "In case the source code is missing a pattern, this node stands in place of the missing pattern.",
traits: [
"MissingNode"
],
children: [
Child(
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
documentation: """
A placeholder, i.e. `<#pattern#>` that can be inserted into the source code to represent the missing pattern.
A placeholder, i.e. `<#pattern#>`, that can be inserted into the source code to represent the missing pattern.
This token should always have `presence = .missing`.
"""
)
Expand All @@ -226,12 +233,15 @@ public let COMMON_NODES: [Node] = [
base: .stmt,
nameForDiagnostics: "statement",
documentation: "In case the source code is missing a statement, this node stands in place of the missing statement.",
traits: [
"MissingNode"
],
children: [
Child(
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
documentation: """
A placeholder, i.e. `<#statement#>` that can be inserted into the source code to represent the missing pattern.
A placeholder, i.e. `<#statement#>`, that can be inserted into the source code to represent the missing pattern.
This token should always have `presence = .missing`.
"""
)
Expand All @@ -243,12 +253,15 @@ public let COMMON_NODES: [Node] = [
base: .syntax,
nameForDiagnostics: nil,
documentation: "In case the source code is missing a syntax node, this node stands in place of the missing node.",
traits: [
"MissingNode"
],
children: [
Child(
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
documentation: """
A placeholder, i.e. `<#syntax#>` that can be inserted into the source code to represent the missing pattern.
A placeholder, i.e. `<#syntax#>`, that can be inserted into the source code to represent the missing pattern.
This token should always have `presence = .missing`
"""
)
Expand All @@ -260,12 +273,15 @@ public let COMMON_NODES: [Node] = [
base: .type,
nameForDiagnostics: "type",
documentation: "In case the source code is missing a type, this node stands in place of the missing type.",
traits: [
"MissingNode"
],
children: [
Child(
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
documentation: """
A placeholder, i.e. `<#type#>` that can be inserted into the source code to represent the missing type. This token should always have `presence = .missing`.
A placeholder, i.e. `<#type#>`, that can be inserted into the source code to represent the missing type. This token should always have `presence = .missing`.
"""
)
]
Expand Down
27 changes: 24 additions & 3 deletions CodeGeneration/Sources/SyntaxSupport/Traits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

public class Trait {
public let traitName: String
public let protocolName: TokenSyntax
public let documentation: SwiftSyntax.Trivia
public let children: [Child]
public let description: String?

init(traitName: String, children: [Child], description: String? = nil) {
init(traitName: String, documentation: String? = nil, children: [Child]) {
self.traitName = traitName
self.protocolName = .identifier("\(traitName)Syntax")
self.documentation = docCommentTrivia(from: documentation)
self.children = children
self.description = description
}
}

Expand Down Expand Up @@ -106,4 +110,21 @@ public let TRAITS: [Trait] = [
Child(name: "TrailingComma", kind: .token(choices: [.token(tokenKind: "CommaToken")]), isOptional: true)
]
),
Trait(
traitName: "MissingNode",
documentation: """
Represents a layout node that is missing in the source file.

See the types conforming to this protocol for examples of where missing nodes can occur.
""",
children: [
Child(
name: "Placeholder",
kind: .token(choices: [.token(tokenKind: "IdentifierToken")]),
documentation: """
A placeholder, i.e. `<#placeholder#>`, that can be inserted into the source code to represent the missing node.
"""
)
]
),
]
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ let nodesSections: String = {

addSection(heading: "Miscellaneous Syntax", types: SYNTAX_NODES.map(\.kind.syntaxType.description).filter({ !handledSyntaxTypes.contains($0) }))

addSection(heading: "Traits", types: TRAITS.map { "\($0.traitName)Syntax" })
addSection(heading: "Traits", types: TRAITS.map { "\($0.protocolName)" })

return result
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,30 @@ let syntaxTraitsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
for trait in TRAITS {
try! ProtocolDeclSyntax(
"""
// MARK: - \(raw: trait.traitName)Syntax
// MARK: - \(trait.protocolName)

public protocol \(raw: trait.traitName)Syntax: SyntaxProtocol
\(raw: trait.documentation)
public protocol \(trait.protocolName): SyntaxProtocol
"""
) {
for child in trait.children {
DeclSyntax("var \(raw: child.varName): \(child.syntaxNodeKind.syntaxType)\(raw: child.isOptional ? "?" : "") { get set }")
DeclSyntax(
"""
\(raw: child.docComment)
var \(raw: child.varName): \(child.syntaxNodeKind.syntaxType)\(raw: child.isOptional ? "?" : "") { get set }
"""
)
}
}

try! ExtensionDeclSyntax("public extension \(raw: trait.traitName)Syntax") {
try! ExtensionDeclSyntax("public extension \(trait.protocolName)") {
DeclSyntax(
"""
/// Without this function, the `with` function defined on `SyntaxProtocol`
/// does not work on existentials of this protocol type.
@_disfavoredOverload
func with<T>(_ keyPath: WritableKeyPath<\(raw: trait.traitName)Syntax, T>, _ newChild: T) -> \(raw: trait.traitName)Syntax {
var copy: \(raw: trait.traitName)Syntax = self
func with<T>(_ keyPath: WritableKeyPath<\(trait.protocolName), T>, _ newChild: T) -> \(trait.protocolName) {
var copy: \(trait.protocolName) = self
copy[keyPath: keyPath] = newChild
return copy
}
Expand All @@ -48,21 +54,21 @@ let syntaxTraitsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
DeclSyntax(
"""
/// Check whether the non-type erased version of this syntax node conforms to
/// `\(raw: trait.traitName)Syntax`.
/// `\(trait.protocolName)`.
/// Note that this will incur an existential conversion.
func isProtocol(_: \(raw: trait.traitName)Syntax.Protocol) -> Bool {
return self.asProtocol(\(raw: trait.traitName)Syntax.self) != nil
func isProtocol(_: \(trait.protocolName).Protocol) -> Bool {
return self.asProtocol(\(trait.protocolName).self) != nil
}
"""
)

DeclSyntax(
"""
/// Return the non-type erased version of this syntax node if it conforms to
/// `\(raw: trait.traitName)Syntax`. Otherwise return `nil`.
/// `\(trait.protocolName)`. Otherwise return `nil`.
/// Note that this will incur an existential conversion.
func asProtocol(_: \(raw: trait.traitName)Syntax.Protocol) -> \(raw: trait.traitName)Syntax? {
return Syntax(self).asProtocol(SyntaxProtocol.self) as? \(raw: trait.traitName)Syntax
func asProtocol(_: \(trait.protocolName).Protocol) -> \(trait.protocolName)? {
return Syntax(self).asProtocol(SyntaxProtocol.self) as? \(trait.protocolName)
}
"""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ class ValidateSyntaxNodes: XCTestCase {
ValidationFailure(node: .availabilityCondition, message: "could conform to trait 'Parenthesized' but does not"),
ValidationFailure(node: .canImportExpr, message: "could conform to trait 'Parenthesized' but does not"),
ValidationFailure(node: .differentiabilityParams, message: "could conform to trait 'Parenthesized' but does not"),
ValidationFailure(node: .editorPlaceholderDecl, message: "could conform to trait 'MissingNode' but does not"),
ValidationFailure(node: .editorPlaceholderExpr, message: "could conform to trait 'IdentifiedDecl' but does not"),
ValidationFailure(node: .enumCaseElement, message: "could conform to trait 'IdentifiedDecl' but does not"),
ValidationFailure(node: .initializesEffect, message: "could conform to trait 'Parenthesized' but does not"),
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftDiagnostics/Diagnostic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public struct Diagnostic: CustomDebugStringConvertible {
public var debugDescription: String {
let locationConverter = SourceLocationConverter(file: "", tree: node.root)
let location = location(converter: locationConverter)
return "\(location): \(message)"
return "\(location.line):\(location.column): \(message)"
}
}

Expand Down
10 changes: 9 additions & 1 deletion Sources/SwiftParserDiagnostics/MissingNodesError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,15 @@ extension ParseDiagnosticsGenerator {
}
}

let changes = missingNodes.map { FixIt.MultiNodeChange.makePresent($0) }
let changes = missingNodes.map { node in
if let missing = node.asProtocol(MissingNodeSyntax.self) {
// For missing nodes, only make the placeholder present. Don’t make any
// missing nodes, e.g. in a malformed attribute, present.
return FixIt.MultiNodeChange.makePresent(missing.placeholder)
} else {
return FixIt.MultiNodeChange.makePresent(node)
}
}
let fixIt = FixIt(
message: InsertTokenFixIt(missingNodes: missingNodes),
changes: additionalChanges + changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ These articles are intended for developers wishing to contribute to SwiftSyntax
- <doc:SwiftSyntax/WithModifiersSyntax>
- <doc:SwiftSyntax/WithStatementsSyntax>
- <doc:SwiftSyntax/WithTrailingCommaSyntax>
- <doc:SwiftSyntax/MissingNodeSyntax>



Expand Down
Loading