Skip to content

Commit 3590c97

Browse files
committed
Prevent Unrelated Casts on Child Choice Node
1 parent af32a12 commit 3590c97

File tree

13 files changed

+1721
-0
lines changed

13 files changed

+1721
-0
lines changed

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxCollectionsFile.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ let syntaxCollectionsFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
112112

113113
StmtSyntax("return .choices(\(choices))")
114114
}
115+
116+
for choiceNodeName in node.elementChoices {
117+
let choiceNode = SYNTAX_NODE_MAP[choiceNodeName]!
118+
choiceNodeCastingMethods(for: choiceNode.kind)
119+
}
115120
}
116121
}
117122

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
import SwiftSyntaxBuilder
15+
import SyntaxSupport
16+
17+
@MemberBlockItemListBuilder
18+
func choiceNodeCastingMethods(for syntaxNodeKind: SyntaxNodeKind) -> MemberBlockItemListSyntax {
19+
if syntaxNodeKind.isBase {
20+
DeclSyntax(
21+
"""
22+
/// Checks if the current syntax node can be cast to the type conforming to the ``\(syntaxNodeKind.protocolType)`` protocol.
23+
///
24+
/// - Returns: `true` if the node can be cast, `false` otherwise.
25+
public func `is`<S: \(syntaxNodeKind.protocolType)>(_ syntaxType: S.Type) -> Bool {
26+
return self.as(syntaxType) != nil
27+
}
28+
"""
29+
)
30+
31+
DeclSyntax(
32+
"""
33+
/// Attempts to cast the current syntax node to the type conforming to the ``\(syntaxNodeKind.protocolType)`` protocol.
34+
///
35+
/// - Returns: An instance of the specialized type, or `nil` if the cast fails.
36+
public func `as`<S: \(syntaxNodeKind.protocolType)>(_ syntaxType: S.Type) -> S? {
37+
return S.init(self)
38+
}
39+
"""
40+
)
41+
42+
DeclSyntax(
43+
"""
44+
/// Force-casts the current syntax node to the type conforming to the ``\(syntaxNodeKind.protocolType)`` protocol.
45+
///
46+
/// - Returns: An instance of the specialized type.
47+
/// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast.
48+
public func cast<S: \(syntaxNodeKind.protocolType)>(_ syntaxType: S.Type) -> S {
49+
return self.as(S.self)!
50+
}
51+
"""
52+
)
53+
} else {
54+
DeclSyntax(
55+
"""
56+
/// Checks if the current syntax node can be cast to ``\(syntaxNodeKind.syntaxType)``.
57+
///
58+
/// - Returns: `true` if the node can be cast, `false` otherwise.
59+
public func `is`(_ syntaxType: \(syntaxNodeKind.syntaxType).Type) -> Bool {
60+
return self.as(syntaxType) != nil
61+
}
62+
"""
63+
)
64+
65+
DeclSyntax(
66+
"""
67+
/// Attempts to cast the current syntax node to ``\(syntaxNodeKind.syntaxType)``.
68+
///
69+
/// - Returns: An instance of ``\(syntaxNodeKind.syntaxType)``, or `nil` if the cast fails.
70+
public func `as`(_ syntaxType: \(syntaxNodeKind.syntaxType).Type) -> \(syntaxNodeKind.syntaxType)? {
71+
return \(syntaxNodeKind.syntaxType).init(self)
72+
}
73+
"""
74+
)
75+
76+
DeclSyntax(
77+
"""
78+
/// Force-casts the current syntax node to ``\(syntaxNodeKind.syntaxType)``.
79+
///
80+
/// - Returns: An instance of ``\(syntaxNodeKind.syntaxType)``.
81+
/// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast.
82+
public func cast(_ syntaxType: \(syntaxNodeKind.syntaxType).Type) -> \(syntaxNodeKind.syntaxType) {
83+
return self.as(\(syntaxNodeKind.syntaxType).self)!
84+
}
85+
"""
86+
)
87+
}
88+
}

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,5 +289,9 @@ private func generateSyntaxChildChoices(for child: Child) throws -> EnumDeclSynt
289289

290290
StmtSyntax("return .choices(\(choices))")
291291
}
292+
293+
for choiceNode in choices {
294+
choiceNodeCastingMethods(for: choiceNode.syntaxNodeKind)
295+
}
292296
}
293297
}

Release Notes/510.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
- Description: `is`, `as`, and `cast` methods on base node protocols with base-type conversions are marked as deprecated. The deprecated methods emit a warning that informs the developer that the cast will always succeed and should be done using the base node's initializer.
5050
- Issue: https://github.com/apple/swift-syntax/issues/2092
5151
- Pull Request: https://github.com/apple/swift-syntax/pull/2108
52+
53+
- Child Choice Node Casts
54+
- Description: `is`, `as`, and `cast` methods for types not contained in the choice node are marked as deprecated. The deprecated methods will emit a warning, indicating that the cast will always fail.
55+
- Issue: https://github.com/apple/swift-syntax/issues/2092
56+
- Pull Request: https://github.com/apple/swift-syntax/pull/2184
5257

5358
## API-Incompatible Changes
5459

Sources/SwiftSyntax/SyntaxProtocol.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,3 +698,38 @@ public extension SyntaxProtocol {
698698
/// Protocol for the enums nested inside ``Syntax`` nodes that enumerate all the
699699
/// possible types a child node might have.
700700
public protocol SyntaxChildChoices: SyntaxProtocol {}
701+
702+
public extension SyntaxChildChoices {
703+
704+
/// Checks if the current ``SyntaxChildChoices`` instance can be cast to a given specialized syntax type.
705+
///
706+
/// - Returns: `true` if the node can be cast, `false` otherwise.
707+
///
708+
/// - Note: This method is marked as deprecated because it is advised not to use it for unrelated casts.
709+
@available(*, deprecated, message: "This cast will always fail")
710+
func `is`<S: SyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
711+
return self.as(syntaxType) != nil
712+
}
713+
714+
/// Attempts to cast the current ``SyntaxChildChoices`` instance to a given specialized syntax type.
715+
///
716+
/// - Returns: An instance of the specialized syntax type, or `nil` if the cast fails.
717+
///
718+
/// - Note: This method is marked as deprecated because it is advised not to use it for unrelated casts.
719+
@available(*, deprecated, message: "This cast will always fail")
720+
func `as`<S: SyntaxProtocol>(_ syntaxType: S.Type) -> S? {
721+
return S.init(self)
722+
}
723+
724+
/// Force-casts the current ``SyntaxChildChoices`` instance to a given specialized syntax type.
725+
///
726+
/// - Returns: An instance of the specialized syntax type.
727+
///
728+
/// - Warning: This function will crash if the cast is not possible. Use `as` for a safe attempt.
729+
///
730+
/// - Note: This method is marked as deprecated because it is advised not to use it for unrelated casts.
731+
@available(*, deprecated, message: "This cast will always fail")
732+
func cast<S: SyntaxProtocol>(_ syntaxType: S.Type) -> S {
733+
return self.as(S.self)!
734+
}
735+
}

0 commit comments

Comments
 (0)