Skip to content

Commit d3c189b

Browse files
committed
Adding existential and opaque support to EntityType
1 parent 358c7b7 commit d3c189b

File tree

5 files changed

+748
-1
lines changed

5 files changed

+748
-1
lines changed

Sources/SyntaxSparrow/Internal/Extensions/EntityType+Parsing.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ extension EntityType {
2828
}
2929
}
3030

31+
32+
var isExistential: Bool {
33+
switch self {
34+
case .existential:
35+
return true
36+
default:
37+
return false
38+
}
39+
}
40+
41+
var isOpaque: Bool {
42+
switch self {
43+
case .opaque:
44+
return true
45+
default:
46+
return false
47+
}
48+
}
49+
3150
var isVoid: Bool {
3251
switch self {
3352
case .void:
@@ -107,6 +126,16 @@ extension EntityType {
107126
return parseType(attributedType.baseType)
108127
}
109128

129+
// Some or Any (Opaque/Existential)
130+
if let someOrAny = typeSyntax.as(SomeOrAnyTypeSyntax.self) {
131+
let resolvedType = parseType(someOrAny.constraint)
132+
if someOrAny.someOrAnySpecifier.tokenKind == .keyword(.any) {
133+
return .existential(resolvedType)
134+
} else {
135+
return .opaque(resolvedType)
136+
}
137+
}
138+
110139
// Fallback
111140
if !typeSyntax.description.trimmed.isEmpty {
112141
let typeString = resolveSimpleTypeString(from: typeSyntax)

Sources/SyntaxSparrow/Public/Semantics/Components/EntityType.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SwiftSyntax
1313
/// By default a ``SyntaxSparrow/EntityType/simple(_:)`` type will be used with a string representation of the declared type.
1414
/// Initial support for some complex types, such as closures, tuples, and results is provided.
1515
/// As support for more complex types are added they will be added as a dedicated enumeration case to the `EntityType`
16-
public enum EntityType: Equatable, Hashable, CustomStringConvertible {
16+
public indirect enum EntityType: Equatable, Hashable, CustomStringConvertible {
1717
/// A `simple` type refers to a standard swift type can't does not have any nested or related syntax.
1818
/// **Note:** This is also used for any unsupported syntax types. i.e `CVarArg` is not currently supported so it will use the
1919
/// `.simple("CVarArg...")`
@@ -102,6 +102,39 @@ public enum EntityType: Equatable, Hashable, CustomStringConvertible {
102102
/// - The third function would have a parameter with the type `.closure(Closure)` where the closure input and output are both `.void`
103103
case void(_ raw: String, _ isOptional: Bool)
104104

105+
/// An `existential` type is used when a parameter's type uses the `any` keyword to represent an existential type.
106+
///
107+
/// Existential types allow for runtime polymorphism by erasing the concrete type behind a protocol constraint.
108+
///
109+
/// For example,
110+
/// ```swift
111+
/// func example(handler: any Sendable) { ... }
112+
/// func example(items: [any Codable]) { ... }
113+
/// ```
114+
/// would have types of `.existential(.simple("Sendable"))` and `.array(ArrayDecl)` where the array's `elementType`
115+
/// is `.existential(.simple("Codable"))` respectively.
116+
///
117+
/// **Note:** The `any` keyword was introduced in Swift 5.7 to explicitly denote existential types, making the distinction
118+
/// between existential and opaque types clear in the type system.
119+
case existential(_ type: EntityType)
120+
121+
/// An `opaque` type is used when a parameter's type uses the `some` keyword to represent an opaque type.
122+
///
123+
/// Opaque types provide compile-time polymorphism by hiding the concrete type behind a protocol constraint while
124+
/// preserving type identity. Unlike existential types, opaque types guarantee the same underlying concrete type
125+
/// is used consistently.
126+
///
127+
/// For example,
128+
/// ```swift
129+
/// func example() -> some View { ... }
130+
/// func example(handler: some Sendable) { ... }
131+
/// ```
132+
/// would have return/parameter types of `.opaque(.simple("View"))` and `.opaque(.simple("Sendable"))` respectively.
133+
///
134+
/// **Note:** Opaque types are commonly used with SwiftUI's `View` protocol and were introduced in Swift 5.1,
135+
/// with the `some` keyword extended to parameter position in Swift 5.7.
136+
case opaque(_ type: EntityType)
137+
105138
/// An `empty` type refers to a when a parameter or property is partially declared and does not have a type defined.
106139
///
107140
/// **Note:** This is not best practice, but as the parser can iterate over these declarations, it avoids having an optional type to work with.
@@ -128,6 +161,10 @@ public enum EntityType: Equatable, Hashable, CustomStringConvertible {
128161
case let .void(rawType, isOptional):
129162
if rawType.hasSuffix("?") { return rawType }
130163
return "\(rawType)\(isOptional ? "?" : "")"
164+
case let .existential(wrapped):
165+
return wrapped.description
166+
case let .opaque(wrapped):
167+
return wrapped.description
131168
case .empty:
132169
return ""
133170
}

Sources/SyntaxSparrow/Public/Semantics/Components/Parameter.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,72 @@ public struct Parameter: Equatable, Hashable, CustomStringConvertible {
134134
/// - Both parameter arguments in the tuple will have `isLabelOmitted` as `false`
135135
public var isLabelOmitted: Bool { name == "_" }
136136

137+
/// A Boolean value indicating whether the variable's type is an existential type (prefixed with `any`).
138+
///
139+
/// Existential types allow for runtime polymorphism by type-erasing the concrete implementation
140+
/// behind a protocol constraint. This property returns `true` when the variable is declared with
141+
/// the `any` keyword.
142+
///
143+
/// For example:
144+
/// ```swift
145+
/// var handler: any Sendable // isExistential = true
146+
/// var items: [any Codable] // isExistential = false (the variable is an array, but contains existential elements)
147+
/// var name: String // isExistential = false
148+
/// ```
149+
///
150+
/// **Note:** This property only checks if the top-level type is existential. For nested existential
151+
/// types (such as array elements), you need to inspect the ``type`` property directly.
152+
public var isExistential: Bool {
153+
type.isExistential
154+
}
155+
156+
/// A Boolean value indicating whether the variable's type is an opaque type (prefixed with `some`).
157+
///
158+
/// Opaque types provide compile-time polymorphism by hiding the concrete type behind a protocol
159+
/// constraint while preserving type identity. This property returns `true` when the variable is
160+
/// declared with the `some` keyword.
161+
///
162+
/// For example:
163+
/// ```swift
164+
/// var view: some View // isOpaque = true
165+
/// var items: [some Sendable] // isOpaque = false (the variable is an array, but contains opaque elements)
166+
/// var name: String // isOpaque = false
167+
/// ```
168+
///
169+
/// **Note:** This property only checks if the top-level type is opaque. For nested opaque types
170+
/// (such as array elements), you need to inspect the ``type`` property directly.
171+
public var isOpaque: Bool {
172+
type.isOpaque
173+
}
174+
175+
/// Returns the existential or opaque type keyword used in the variable's type declaration, if present.
176+
///
177+
/// This property returns `"any"` for existential types, `"some"` for opaque types, or `nil` if the
178+
/// variable's type is neither existential nor opaque.
179+
///
180+
/// For example:
181+
/// ```swift
182+
/// var handler: any Sendable // someOrAnyKeyword = "any"
183+
/// var view: some View // someOrAnyKeyword = "some"
184+
/// var name: String // someOrAnyKeyword = nil
185+
/// var items: [any Codable] // someOrAnyKeyword = nil (top-level type is array)
186+
/// ```
187+
///
188+
/// **Note:** This property only checks the top-level type. For nested existential or opaque types
189+
/// (such as `[any Codable]` or `(some View) -> Void`), this returns `nil` because the variable's
190+
/// direct type is the collection or closure, not the existential/opaque type itself.
191+
///
192+
/// - Returns: `"any"` if the type is existential, `"some"` if the type is opaque, or `nil` otherwise.
193+
public var someOrAnyKeyword: String? {
194+
if isExistential {
195+
return "any"
196+
} else if isOpaque {
197+
return "some"
198+
} else {
199+
return nil
200+
}
201+
}
202+
137203
// MARK: - Properties: Resolving
138204

139205
private(set) var resolver: any ParameterNodeSemanticsResolving

Sources/SyntaxSparrow/Public/Semantics/Declarations/Variable.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,72 @@ public struct Variable: Declaration {
164164
return accessors.contains(where: { $0.kind == .get && $0.effectSpecifiers?.asyncSpecifier != nil })
165165
}
166166

167+
/// A Boolean value indicating whether the variable's type is an existential type (prefixed with `any`).
168+
///
169+
/// Existential types allow for runtime polymorphism by type-erasing the concrete implementation
170+
/// behind a protocol constraint. This property returns `true` when the variable is declared with
171+
/// the `any` keyword.
172+
///
173+
/// For example:
174+
/// ```swift
175+
/// var handler: any Sendable // isExistential = true
176+
/// var items: [any Codable] // isExistential = false (the variable is an array, but contains existential elements)
177+
/// var name: String // isExistential = false
178+
/// ```
179+
///
180+
/// **Note:** This property only checks if the top-level type is existential. For nested existential
181+
/// types (such as array elements), you need to inspect the ``type`` property directly.
182+
public var isExistential: Bool {
183+
type.isExistential
184+
}
185+
186+
/// A Boolean value indicating whether the variable's type is an opaque type (prefixed with `some`).
187+
///
188+
/// Opaque types provide compile-time polymorphism by hiding the concrete type behind a protocol
189+
/// constraint while preserving type identity. This property returns `true` when the variable is
190+
/// declared with the `some` keyword.
191+
///
192+
/// For example:
193+
/// ```swift
194+
/// var view: some View // isOpaque = true
195+
/// var items: [some Sendable] // isOpaque = false (the variable is an array, but contains opaque elements)
196+
/// var name: String // isOpaque = false
197+
/// ```
198+
///
199+
/// **Note:** This property only checks if the top-level type is opaque. For nested opaque types
200+
/// (such as array elements), you need to inspect the ``type`` property directly.
201+
public var isOpaque: Bool {
202+
type.isOpaque
203+
}
204+
205+
/// Returns the existential or opaque type keyword used in the variable's type declaration, if present.
206+
///
207+
/// This property returns `"any"` for existential types, `"some"` for opaque types, or `nil` if the
208+
/// variable's type is neither existential nor opaque.
209+
///
210+
/// For example:
211+
/// ```swift
212+
/// var handler: any Sendable // someOrAnyKeyword = "any"
213+
/// var view: some View // someOrAnyKeyword = "some"
214+
/// var name: String // someOrAnyKeyword = nil
215+
/// var items: [any Codable] // someOrAnyKeyword = nil (top-level type is array)
216+
/// ```
217+
///
218+
/// **Note:** This property only checks the top-level type. For nested existential or opaque types
219+
/// (such as `[any Codable]` or `(some View) -> Void`), this returns `nil` because the variable's
220+
/// direct type is the collection or closure, not the existential/opaque type itself.
221+
///
222+
/// - Returns: `"any"` if the type is existential, `"some"` if the type is opaque, or `nil` otherwise.
223+
public var someOrAnyKeyword: String? {
224+
if isExistential {
225+
return "any"
226+
} else if isOpaque {
227+
return "some"
228+
} else {
229+
return nil
230+
}
231+
}
232+
167233
// MARK: - Properties: DeclarationCollecting
168234

169235
private(set) var resolver: VariableSemanticsResolver

0 commit comments

Comments
 (0)