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

Add TypeSyntax util functions #2886

Open
wants to merge 2 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
10 changes: 0 additions & 10 deletions Sources/SwiftRefactor/SyntaxUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,3 @@ extension Trivia {
return Trivia(pieces: self.reversed().drop(while: \.isWhitespace).reversed())
}
}

extension TypeSyntax {
var isVoid: Bool {
switch self.as(TypeSyntaxEnum.self) {
case .identifierType(let identifierType) where identifierType.name.text == "Void": return true
case .tupleType(let tupleType) where tupleType.elements.isEmpty: return true
default: return false
}
}
}
62 changes: 62 additions & 0 deletions Sources/SwiftSyntax/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,65 @@ extension RawUnexpectedNodesSyntax {
self.init(raw: raw)
}
}

extension TypeSyntaxProtocol {
/// Check if this syntax matches any of the standard names for `Void`:
/// * Void
/// * Swift.Void
/// * ()
public var isVoid: Bool {
Copy link
Member

Choose a reason for hiding this comment

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

Please provide documentation comments for all public APIs. Once we settle on them, please add an entry to the release notes.

if let identifierType = self.as(IdentifierTypeSyntax.self) {
return identifierType.name.text == "Void"
}
if let memberType = self.as(MemberTypeSyntax.self) {
return memberType.baseType.isSwiftCoreModule && memberType.name.text == "Void"
}
if let tupleType = self.as(TupleTypeSyntax.self) {
return tupleType.elements.isEmpty
}
return false
}

var isSwiftCoreModule: Bool {
guard let identifierType = self.as(IdentifierTypeSyntax.self) else {
return false
}
return identifierType.name.text == "Swift"
}

/// Check if this syntax could resolve to the type passed. Only supports types where the canonical type
/// can be named using only IdentifierTypeSyntax and MemberTypeSyntax. A non-exhaustive list of unsupported
/// types includes:
/// * array types
/// * function types
/// * optional types
/// * tuple types (including Void!)
/// The type syntax is allowed to use any level of qualified name for the type, e.g. Swift.Int.self
/// will match against both "Swift.Int" and "Int".
///
/// - Parameter type: Type to check against. NB: if passing a type alias, the canonical type will be used.
/// - Returns: true if `self` spells out some suffix of the fully qualified name of `type`, otherwise false
public func canRepresentBasicType(type: Any.Type) -> Bool {
let qualifiedTypeName = String(reflecting: type)
var typeNames = qualifiedTypeName.split(separator: ".")
var currType: TypeSyntaxProtocol = self

while !typeNames.isEmpty {
let typeName = typeNames.popLast()!
if let identifierType = currType.as(IdentifierTypeSyntax.self) {
// It doesn't matter whether this is the final element of typeNames, because we don't know
// surrounding context - the Foo.Bar.Baz type can be referred to as `Baz` inside Foo.Bar
return identifierType.name.text == typeName
} else if let memberType = currType.as(MemberTypeSyntax.self) {
if memberType.name.text != typeName {
return false
}
currType = memberType.baseType
} else {
return false
}
}

return false
}
}
76 changes: 76 additions & 0 deletions Tests/SwiftSyntaxTest/UtilsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
import XCTest

class UtilsTests: XCTestCase {

public func testIsVoid() {
XCTAssertTrue(TypeSyntax("Void").isVoid)
XCTAssertTrue(TypeSyntax("Swift.Void").isVoid)
XCTAssertTrue(TypeSyntax("()").isVoid)

XCTAssertFalse(TypeSyntax("(Int, Int)").isVoid)
XCTAssertFalse(TypeSyntax("Swift").isVoid)
XCTAssertFalse(TypeSyntax("Swift.()").isVoid)
XCTAssertFalse(TypeSyntax("Int").isVoid)
XCTAssertFalse(TypeSyntax("(())").isVoid)
XCTAssertFalse(TypeSyntax("(Void)").isVoid)
}

public func testIsInt() {
XCTAssertTrue(TypeSyntax("Int").canRepresentBasicType(type: Int.self))
XCTAssertTrue(TypeSyntax("Swift.Int").canRepresentBasicType(type: Int.self))
XCTAssertTrue(TypeSyntax("Int").canRepresentBasicType(type: Swift.Int.self))
XCTAssertTrue(TypeSyntax("Swift.Int").canRepresentBasicType(type: Swift.Int.self))

// Only the canonical type syntax matches
XCTAssertFalse(TypeSyntax("CInt").canRepresentBasicType(type: Int.self))
XCTAssertFalse(TypeSyntax("Swift.CInt").canRepresentBasicType(type: Int.self))
XCTAssertFalse(TypeSyntax("CInt").canRepresentBasicType(type: Swift.Int.self))
XCTAssertFalse(TypeSyntax("Swift.CInt").canRepresentBasicType(type: Swift.Int.self))
}

public func testIsCInt() {
// Match against the canonical type (platform dependent)
XCTAssertEqual(TypeSyntax("Swift.Int").canRepresentBasicType(type: Swift.CInt.self), CInt.self == Int.self)
XCTAssertEqual(TypeSyntax("Int").canRepresentBasicType(type: Swift.CInt.self), CInt.self == Int.self)
XCTAssertEqual(TypeSyntax("Int").canRepresentBasicType(type: CInt.self), CInt.self == Int.self)
XCTAssertEqual(TypeSyntax("Swift.Int").canRepresentBasicType(type: CInt.self), CInt.self == Int.self)

XCTAssertFalse(TypeSyntax("Swift.CInt").canRepresentBasicType(type: Swift.CInt.self))
XCTAssertFalse(TypeSyntax("CInt").canRepresentBasicType(type: Swift.CInt.self))
XCTAssertFalse(TypeSyntax("CInt").canRepresentBasicType(type: CInt.self))
XCTAssertFalse(TypeSyntax("Swift.CInt").canRepresentBasicType(type: CInt.self))
}

public func testIsArrayType() {
// Only plain name types are supported
XCTAssertFalse(TypeSyntax("[Int]").canRepresentBasicType(type: [Int].self))
XCTAssertFalse(TypeSyntax("Int").canRepresentBasicType(type: [Int].self))
}

public func testIsOptionalType() {
// Only plain name types are supported
XCTAssertFalse(TypeSyntax("Int?").canRepresentBasicType(type: Int?.self))
XCTAssertFalse(TypeSyntax("Optional<Int>").canRepresentBasicType(type: Int?.self))
XCTAssertFalse(TypeSyntax("Int").canRepresentBasicType(type: [Int].self))
}

public func testIsTupleTypes() {
// Only plain name types are supported
XCTAssertFalse(TypeSyntax("()").canRepresentBasicType(type: Void.self))
XCTAssertFalse(TypeSyntax("Void").canRepresentBasicType(type: Void.self))
XCTAssertFalse(TypeSyntax("(Int, Int)").canRepresentBasicType(type: (Int, Int).self))
}
}