From 7eeaf2be0ea803e933ceba61b663c07ff2661daf Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Mon, 28 Oct 2024 18:27:16 -0700 Subject: [PATCH 1/2] Add TypeSyntax util functions This moves isVoid from SwiftRefactor to be publicly accessible in SwiftSyntax. Also adds isSwiftCoreModule and isSwiftInt and calls isSwiftCoreModule in isVoid and isSwiftInt to properly handle the qualified cases of Swift.Void and Swift.Int. --- Sources/SwiftRefactor/SyntaxUtils.swift | 10 ---------- Sources/SwiftSyntax/Utils.swift | 26 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftRefactor/SyntaxUtils.swift b/Sources/SwiftRefactor/SyntaxUtils.swift index 62a108c325c..d8ebdc3099e 100644 --- a/Sources/SwiftRefactor/SyntaxUtils.swift +++ b/Sources/SwiftRefactor/SyntaxUtils.swift @@ -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 - } - } -} diff --git a/Sources/SwiftSyntax/Utils.swift b/Sources/SwiftSyntax/Utils.swift index 3be00281ca9..80979baf9f4 100644 --- a/Sources/SwiftSyntax/Utils.swift +++ b/Sources/SwiftSyntax/Utils.swift @@ -102,3 +102,29 @@ extension RawUnexpectedNodesSyntax { self.init(raw: raw) } } + +extension TypeSyntax { + public 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 + case .memberType(let memberType) where memberType.name.text == "Void": return memberType.baseType.isSwiftCoreModule + default: return false + } + } + + public var isSwiftInt: Bool { + switch self.as(TypeSyntaxEnum.self) { + case .identifierType(let identifierType) where identifierType.name.text == "Int": return true + case .memberType(let memberType) where memberType.name.text == "Int": return memberType.baseType.isSwiftCoreModule + default: return false + } + } + + public var isSwiftCoreModule: Bool { + guard let identifierType = self.as(IdentifierTypeSyntax.self) else { + return false + } + return identifierType.name.text == "Swift" + } +} From 96d8330d8aec6799c9e458cbfa96339dfa467186 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Tue, 29 Oct 2024 18:05:55 -0700 Subject: [PATCH 2/2] Replace isSwiftInt with canRepresentBasicType() canRepresentBasicType() can match against any canonical type named by only dots and identifiers. --- Sources/SwiftSyntax/Utils.swift | 64 +++++++++++++++++----- Tests/SwiftSyntaxTest/UtilsTests.swift | 76 ++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 Tests/SwiftSyntaxTest/UtilsTests.swift diff --git a/Sources/SwiftSyntax/Utils.swift b/Sources/SwiftSyntax/Utils.swift index 80979baf9f4..a2a507ee969 100644 --- a/Sources/SwiftSyntax/Utils.swift +++ b/Sources/SwiftSyntax/Utils.swift @@ -103,28 +103,64 @@ extension RawUnexpectedNodesSyntax { } } -extension TypeSyntax { +extension TypeSyntaxProtocol { + /// Check if this syntax matches any of the standard names for `Void`: + /// * Void + /// * Swift.Void + /// * () public 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 - case .memberType(let memberType) where memberType.name.text == "Void": return memberType.baseType.isSwiftCoreModule - default: return false + if let identifierType = self.as(IdentifierTypeSyntax.self) { + return identifierType.name.text == "Void" } - } - - public var isSwiftInt: Bool { - switch self.as(TypeSyntaxEnum.self) { - case .identifierType(let identifierType) where identifierType.name.text == "Int": return true - case .memberType(let memberType) where memberType.name.text == "Int": return memberType.baseType.isSwiftCoreModule - default: return false + 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 } - public var isSwiftCoreModule: Bool { + 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 + } } diff --git a/Tests/SwiftSyntaxTest/UtilsTests.swift b/Tests/SwiftSyntaxTest/UtilsTests.swift new file mode 100644 index 00000000000..c90fc41bc08 --- /dev/null +++ b/Tests/SwiftSyntaxTest/UtilsTests.swift @@ -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").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)) + } +} \ No newline at end of file