You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Introduce leaf protocols to prevent leaf nodes from being casted
This commit addresses an issue in the implementation of `SyntaxProtocol` where the 'as' method allows casting to any other syntax node type. This leads to problematic scenarios, such as casting a 'FunctionDeclSyntax' to an 'IdentifierExprSyntax' without compiler warnings, despite the cast being destined to fail at runtime.
To solve this, specialized leaf protocols have been introduced. These restrict casting options to only those that are meaningful within the same base node type, thereby enhancing type safety and reducing the risk of runtime errors.
@@ -31,6 +31,84 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
31
31
"""
32
32
)
33
33
34
+
DeclSyntax(
35
+
#"""
36
+
/// Extension of ``\#(node.kind.protocolType)`` to provide casting methods.
37
+
///
38
+
/// These methods enable casting between syntax node types within the same
39
+
/// base node protocol hierarchy (e.g., ``DeclSyntaxProtocol``).
40
+
///
41
+
/// While ``SyntaxProtocol`` offers general casting methods (``SyntaxProtocol.as(_:)``,
42
+
/// ``SyntaxProtocol.is(_:)``, and ``SyntaxProtocol.cast(_:)``), these often aren't
43
+
/// appropriate for use on types conforming to a specific base node protocol
44
+
/// like ``\#(node.kind.protocolType)``. That's because at this level,
45
+
/// we know that the cast to another base node type (e.g., ``DeclSyntaxProtocol``
46
+
/// when working with ``ExprSyntaxProtocol``) is guaranteed to fail.
47
+
///
48
+
/// To guide developers toward correct usage, this extension provides overloads
49
+
/// of these casting methods that are restricted to the same base node type.
50
+
/// Furthermore, it marks the inherited casting methods from ``SyntaxProtocol`` as
51
+
/// deprecated, indicating that they will always fail when used in this context.
52
+
extension \#(node.kind.protocolType){
53
+
/// Checks if the current syntax node can be cast to a given specialized syntax type.
54
+
///
55
+
/// - Returns: `true` if the node can be cast, `false` otherwise.
56
+
public func `is`<S: \#(node.kind.protocolType)>(_ syntaxType: S.Type) -> Bool {
57
+
return self.as(syntaxType) != nil
58
+
}
59
+
60
+
/// Attempts to cast the current syntax node to a given specialized syntax type.
61
+
///
62
+
/// - Returns: An instance of the specialized type, or `nil` if the cast fails.
63
+
public func `as`<S: \#(node.kind.protocolType)>(_ syntaxType: S.Type) -> S? {
64
+
return S.init(self)
65
+
}
66
+
67
+
/// Force-casts the current syntax node to a given specialized syntax type.
68
+
///
69
+
/// - Returns: An instance of the specialized type.
70
+
/// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast.
71
+
public func cast<S: \#(node.kind.protocolType)>(_ syntaxType: S.Type) -> S {
72
+
return self.as(S.self)!
73
+
}
74
+
75
+
/// Checks if the current syntax node can be cast to a given node type from the different base node protocol hierarchy than ``\#(node.kind.protocolType)``.
76
+
///
77
+
/// - Returns: `false` since the node can not be cast to the node type from different base node protocol hierarchy than ``\#(node.kind.protocolType)``.
78
+
///
79
+
/// - Note: This method overloads the general `is` method and is marked as deprecated to produce a warning,
80
+
/// informing the user that the cast will always fail.
81
+
@available(*, deprecated, message: "This cast will always fail")
82
+
public func `is`<S: SyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
83
+
return false
84
+
}
85
+
86
+
/// Attempts to cast the current syntax node to a given node type from the different base node protocol hierarchy than ``\#(node.kind.protocolType)``.
87
+
///
88
+
/// - Returns: `nil` since the node can not be cast to the node type from different base node protocol hierarchy than ``\#(node.kind.protocolType)``.
89
+
///
90
+
/// - Note: This method overloads the general `as` method and is marked as deprecated to produce a warning,
91
+
/// informing the user that the cast will always fail.
92
+
@available(*, deprecated, message: "This cast will always fail")
93
+
public func `as`<S: SyntaxProtocol>(_ syntaxType: S.Type) -> S? {
94
+
return nil
95
+
}
96
+
97
+
/// Force-casts the current syntax node to a given node type from the different base node protocol hierarchy than ``\#(node.kind.protocolType)``.
98
+
///
99
+
/// - Returns: This method will always trigger a runtime crash and never return.
100
+
///
101
+
/// - Note: This method overloads the general `cast` method and is marked as deprecated to produce a warning,
102
+
/// informing the user that the cast will always fail.
103
+
/// - Warning: Invoking this method will lead to a fatal error.
104
+
@available(*, deprecated, message: "This cast will always fail")
105
+
public func cast<S: SyntaxProtocol>(_ syntaxType: S.Type) -> S {
106
+
fatalError("\(Self.self) cannot be cast to \(S.self)")
Copy file name to clipboardExpand all lines: Release Notes/510.md
+5Lines changed: 5 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,6 +25,11 @@
25
25
26
26
## Deprecations
27
27
28
+
- Leaf Node Casts
29
+
- Description: Syntax nodes that do not act as base nodes for other syntax types have the casting methods marked as deprecated. This prevents unsafe type-casting by issuing deprecation warnings for methods that will always result in failed casts.
0 commit comments