Skip to content
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

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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
128 changes: 128 additions & 0 deletions Sources/MacroToolkit/NormalizedType.swift
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 {
Copy link
Owner

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 NormalizedSimpleTypes so that they can be used as if they've been normalized.

Copy link
Contributor Author

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 on CompositionType, that has no direct way to access the sub-types. Should I still implement this property on NormalizedCompositionType?

public var _baseSyntax: CompositionTypeSyntax
public var _attributedSyntax: AttributedTypeSyntax?

public init(_ syntax: CompositionTypeSyntax, attributedSyntax: AttributedTypeSyntax? = nil) {
_baseSyntax = syntax
_attributedSyntax = attributedSyntax
}
}
25 changes: 25 additions & 0 deletions Sources/MacroToolkit/NormalizedTypes/NormalizedFunctionType.swift
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
}
}
15 changes: 15 additions & 0 deletions Sources/MacroToolkit/NormalizedTypes/NormalizedMemberType.swift
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 {
Copy link
Owner

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 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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as #15 (comment) above, MemberType does not have any direct way to access such properties.

public var _baseSyntax: MemberTypeSyntax
public var _attributedSyntax: AttributedTypeSyntax?

public init(
_ syntax: MemberTypeSyntax,
attributedSyntax: AttributedTypeSyntax? = nil
) {
_baseSyntax = syntax
_attributedSyntax = attributedSyntax
}
}
13 changes: 13 additions & 0 deletions Sources/MacroToolkit/NormalizedTypes/NormalizedMissingType.swift
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
}
}
28 changes: 28 additions & 0 deletions Sources/MacroToolkit/NormalizedTypes/NormalizedSimpleType.swift
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)
}
}
}
15 changes: 15 additions & 0 deletions Sources/MacroToolkit/NormalizedTypes/NormalizedSomeOrAnyType.swift
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
}
}
19 changes: 19 additions & 0 deletions Sources/MacroToolkit/NormalizedTypes/NormalizedTupleType.swift
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)
}
}
}
Loading