-
-
Notifications
You must be signed in to change notification settings - Fork 15
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
Implement type normalization #15
base: main
Are you sure you want to change the base?
Changes from all commits
fffb5b5
644ed70
9302ec9
056ba4d
2640182
0b78983
2061904
bb99f98
b39e8e6
fa39ef0
fc53c29
c1de1a5
71aff30
b135099
206fe0a
d5b047a
84c2bd6
b4a470c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import SwiftSyntax | ||
import SwiftSyntaxBuilder | ||
|
||
public enum NormalizedType: TypeProtocol, SyntaxExpressibleByStringInterpolation { | ||
/// A composition of two types (e.g. `Encodable & Decodable`). Used to | ||
/// combine protocol requirements. | ||
case composition(NormalizedCompositionType) | ||
/// A some or any protocol type (e.g. `any T` or `some T`). | ||
case someOrAny(NormalizedSomeOrAnyType) | ||
/// A function type (e.g. `() -> ()`). | ||
case function(NormalizedFunctionType) | ||
/// An implicitly unwrapped optional type (e.g. `Int!`). | ||
case implicitlyUnwrappedOptional(NormalizedImplicitlyUnwrappedOptionalType) | ||
/// A member type (e.g. `Array<Int>.Element`). | ||
case member(NormalizedMemberType) | ||
/// A placeholder for invalid types that the resilient parser ignored. | ||
case missing(NormalizedMissingType) | ||
/// A pack expansion type (e.g. `repeat each V`). | ||
case packExpansion(NormalizedPackExpansionType) | ||
/// A pack reference type (e.g. `each V`). | ||
case packReference(NormalizedPackReferenceType) | ||
/// A simple type (e.g. `Int` or `Box<Int>`). | ||
case simple(NormalizedSimpleType) | ||
/// A suppressed type in a conformance position (e.g. `~Copyable`). | ||
case suppressed(NormalizedSuppressedType) | ||
//// A tuple type (e.g. `(Int, String)`). | ||
case tuple(NormalizedTupleType) | ||
|
||
public var _baseSyntax: TypeSyntax { | ||
let type: any TypeProtocol = switch self { | ||
case .composition(let type as any TypeProtocol), | ||
.someOrAny(let type as any TypeProtocol), | ||
.function(let type as any TypeProtocol), | ||
.implicitlyUnwrappedOptional(let type as any TypeProtocol), | ||
.member(let type as any TypeProtocol), | ||
.missing(let type as any TypeProtocol), | ||
.packExpansion(let type as any TypeProtocol), | ||
.packReference(let type as any TypeProtocol), | ||
.simple(let type as any TypeProtocol), | ||
.suppressed(let type as any TypeProtocol), | ||
.tuple(let type as any TypeProtocol): | ||
type | ||
} | ||
return TypeSyntax(type._baseSyntax) | ||
} | ||
|
||
public var _attributedSyntax: AttributedTypeSyntax? { | ||
let type: any TypeProtocol = switch self { | ||
case .composition(let type as any TypeProtocol), | ||
.someOrAny(let type as any TypeProtocol), | ||
.function(let type as any TypeProtocol), | ||
.implicitlyUnwrappedOptional(let type as any TypeProtocol), | ||
.member(let type as any TypeProtocol), | ||
.missing(let type as any TypeProtocol), | ||
.packExpansion(let type as any TypeProtocol), | ||
.packReference(let type as any TypeProtocol), | ||
.simple(let type as any TypeProtocol), | ||
.suppressed(let type as any TypeProtocol), | ||
.tuple(let type as any TypeProtocol): | ||
type | ||
} | ||
return type._attributedSyntax | ||
} | ||
|
||
/// Wrap a `TypeSyntax` (e.g. `Int?` or `MyStruct<[String]>!`). | ||
public init(_ syntax: TypeSyntax) { | ||
self.init(syntax, attributedSyntax: nil) | ||
} | ||
|
||
public init(_ syntax: TypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
// TODO: Move this weird initializer to an internal protocol if possible | ||
let syntax: TypeSyntaxProtocol = attributedSyntax ?? syntax | ||
if let type = NormalizedCompositionType(syntax) { | ||
self = .composition(type) | ||
} else if let type = NormalizedSomeOrAnyType(syntax) { | ||
self = .someOrAny(type) | ||
} else if let type = NormalizedFunctionType(syntax) { | ||
self = .function(type) | ||
} else if let type = NormalizedImplicitlyUnwrappedOptionalType(syntax) { | ||
self = .implicitlyUnwrappedOptional(type) | ||
} else if let type = NormalizedMemberType(syntax) { | ||
self = .member(type) | ||
} else if let type = NormalizedPackExpansionType(syntax) { | ||
self = .packExpansion(type) | ||
} else if let type = NormalizedPackReferenceType(syntax) { | ||
self = .packReference(type) | ||
} else if let type = NormalizedSimpleType(syntax) { | ||
self = .simple(type) | ||
} else if let type = NormalizedSuppressedType(syntax) { | ||
self = .suppressed(type) | ||
} else if let type = NormalizedTupleType(syntax) { | ||
self = .tuple(type) | ||
} else { | ||
fatalError("TODO: Implement wrappers for all types of type syntax") | ||
} | ||
} | ||
|
||
// TODO: add an optional version to all type syntax wrappers maybe? | ||
/// Allows string interpolation syntax to be used to express type syntax. | ||
public init(stringInterpolation: SyntaxStringInterpolation) { | ||
self.init(TypeSyntax(stringInterpolation: stringInterpolation)) | ||
} | ||
|
||
/// Gets whether the type is optional | ||
public var isOptional: Bool { | ||
if case .simple(let simpleType) = self { | ||
return simpleType.name == "Optional" | ||
} | ||
return false | ||
} | ||
|
||
// TODO: Generate type conversions with macro? | ||
/// Attempts to get the type as a simple type. | ||
public var asSimpleType: NormalizedSimpleType? { | ||
switch self { | ||
case .simple(let type): type | ||
default: nil | ||
} | ||
} | ||
|
||
/// Attempts to get the type as a function type. | ||
public var asFunctionType: NormalizedFunctionType? { | ||
switch self { | ||
case .function(let type): type | ||
default: nil | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a composition type (e.g. `ProtocolA & ProtocolB`). | ||
public struct NormalizedCompositionType: TypeProtocol { | ||
public var _baseSyntax: CompositionTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: CompositionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a function type (e.g. `(Int, Double) -> Bool`). | ||
public struct NormalizedFunctionType: TypeProtocol { | ||
// TODO: Should give access to attributes such as `@escaping`. | ||
public var _baseSyntax: FunctionTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
/// Don't supply the `attributedSyntax` parameter, use the `attributedSyntax` initializer instead. | ||
/// It only exists because of protocol conformance. | ||
public init(_ syntax: FunctionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
|
||
/// The return type that the function type describes. | ||
public var returnType: NormalizedType { | ||
NormalizedType(_baseSyntax.returnClause.type) | ||
} | ||
|
||
/// The types of the parameters the function type describes. | ||
public var parameters: [NormalizedType] { | ||
_baseSyntax.parameters.map(\.type).map(NormalizedType.init) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps an implicitly unwrapped optional type (e.g. `Int!`). | ||
public struct NormalizedImplicitlyUnwrappedOptionalType: TypeProtocol { | ||
public var _baseSyntax: ImplicitlyUnwrappedOptionalTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: ImplicitlyUnwrappedOptionalTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a member type (e.g. `Array<Int>.Element`). | ||
public struct NormalizedMemberType: TypeProtocol { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a way to access the root and member types (as normalized types). Same goes for any other normalized types which don't have such properties yet (they should have a simple way to pull out their normalized component parts). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as #15 (comment) above, |
||
public var _baseSyntax: MemberTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init( | ||
_ syntax: MemberTypeSyntax, | ||
attributedSyntax: AttributedTypeSyntax? = nil | ||
) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a missing type (i.e. a type that was missing in the source but the resilient parser | ||
/// has added a placeholder for). | ||
public struct NormalizedMissingType: TypeProtocol { | ||
public var _baseSyntax: MissingTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: MissingTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a pack expansion type (e.g. `repeat each V`). | ||
public struct NormalizedPackExpansionType: TypeProtocol { | ||
public var _baseSyntax: PackExpansionTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: PackExpansionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps an implicitly unwrapped optional type (e.g. `each V`). | ||
public struct NormalizedPackReferenceType: TypeProtocol { | ||
public var _baseSyntax: PackElementTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: PackElementTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a simple type (e.g. `Result<Success, Failure>`). | ||
public struct NormalizedSimpleType: TypeProtocol { | ||
public var _baseSyntax: IdentifierTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init( | ||
_ syntax: IdentifierTypeSyntax, | ||
attributedSyntax: AttributedTypeSyntax? = nil | ||
) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
|
||
/// The base type's name (e.g. for `Array<Int>` it would be `"Array"`). | ||
public var name: String { | ||
_baseSyntax.name.description | ||
} | ||
|
||
/// The type's generic arguments if any were supplied (e.g. for | ||
/// `Dictionary<Int, String>` it would be `["Int", "String"]`). | ||
public var genericArguments: [NormalizedType]? { | ||
_baseSyntax.genericArgumentClause.map { clause in | ||
clause.arguments.map(\.argument).map(NormalizedType.init) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a `some` or `any` type (i.e. `any Protocol` or `some Protocol`). | ||
public struct NormalizedSomeOrAnyType: TypeProtocol { | ||
public var _baseSyntax: SomeOrAnyTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init( | ||
_ syntax: SomeOrAnyTypeSyntax, | ||
attributedSyntax: AttributedTypeSyntax? = nil | ||
) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a suppressed type from a conformance clause (e.g. `~Copyable`). | ||
public struct NormalizedSuppressedType: TypeProtocol { | ||
public var _baseSyntax: SuppressedTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: SuppressedTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import SwiftSyntax | ||
|
||
/// Wraps a tuple type (e.g. `(Int, String)`). | ||
public struct NormalizedTupleType: TypeProtocol { | ||
public var _baseSyntax: TupleTypeSyntax | ||
public var _attributedSyntax: AttributedTypeSyntax? | ||
|
||
public init(_ syntax: TupleTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) { | ||
_baseSyntax = syntax | ||
_attributedSyntax = attributedSyntax | ||
} | ||
|
||
var elements: [NormalizedType] { | ||
// TODO: Handle labels and the possible ellipsis | ||
_baseSyntax.elements.map { element in | ||
NormalizedType(element.type) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a way to access the types that have been composed. Even though they don't need to be recursively normalized, I think it'd still be good to represent them as
NormalizedSimpleType
s so that they can be used as if they've been normalized.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I based the implementation of
NormalizedCompositionType
onCompositionType
, that has no direct way to access the sub-types. Should I still implement this property onNormalizedCompositionType
?