Skip to content

Commit

Permalink
Introduce TypeName type and use AssemblyLoadContext.resolveType
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Sep 8, 2024
1 parent 266e020 commit ba90c8a
Show file tree
Hide file tree
Showing 17 changed files with 137 additions and 98 deletions.
3 changes: 1 addition & 2 deletions Sources/DotNetMetadata/Assembly+resolve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ extension Assembly {
return try context.resolveType(
assembly: assemblyReference.identity,
assemblyFlags: assemblyReference.flags,
namespace: namespace,
name: name)
name: TypeName(namespace: namespace, shortName: name))
default:
fatalError("Not implemented: resolution scope \(row.resolutionScope)")
}
Expand Down
10 changes: 4 additions & 6 deletions Sources/DotNetMetadata/Assembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,12 @@ public class Assembly: CustomDebugStringConvertible {
return nil
}

public func resolveTypeDefinition(namespace: String?, name: String, allowForwarding: Bool = true) throws -> TypeDefinition? {
let fullName = makeFullTypeName(namespace: namespace, name: name)
return try resolveTypeDefinition(fullName: fullName, allowForwarding: allowForwarding)
public func resolveTypeDefinition(name: TypeName, allowForwarding: Bool = true) throws -> TypeDefinition? {
try resolveTypeDefinition(fullName: name.fullName, allowForwarding: allowForwarding)
}

public func resolveTypeDefinition(namespace: String?, enclosingName: String, nestedNames: [String], allowForwarding: Bool = true) throws -> TypeDefinition? {
let fullName = makeFullTypeName(namespace: namespace, enclosingName: enclosingName, nestedNames: nestedNames)
return try resolveTypeDefinition(fullName: fullName, allowForwarding: allowForwarding)
public func resolveTypeDefinition(namespace: String, name: String, allowForwarding: Bool = true) throws -> TypeDefinition? {
try resolveTypeDefinition(name: TypeName(namespace: namespace, shortName: name), allowForwarding: allowForwarding)
}

internal func getAttributes(owner: CodedIndices.HasCustomAttribute) -> [Attribute] {
Expand Down
19 changes: 8 additions & 11 deletions Sources/DotNetMetadata/AssemblyLoadContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public final class AssemblyLoadContext {
public init(
referenceResolver: AssemblyReferenceResolver? = nil) {
self.referenceResolver = referenceResolver ?? { identity, _ in
throw AssemblyLoadError.notFound(message: "Reference to identity \(identity) could not be resolved. No assembly reference resolver was provided.")
throw AssemblyLoadError.notFound(message: "Reference to assembly '\(identity)' could not be resolved. No assembly reference resolver was provided.")
}
}

Expand Down Expand Up @@ -120,26 +120,23 @@ public final class AssemblyLoadContext {

internal func resolveType(
assembly assemblyIdentity: AssemblyIdentity,
assemblyFlags: AssemblyFlags,
namespace: String?,
name: String) throws -> TypeDefinition {
assemblyFlags: AssemblyFlags?,
name: TypeName) throws -> TypeDefinition {
// References to UWP assemblies can be inconsistent depending on how the WinMD was built:
// - To contract assemblies, e.g. "Windows.Foundation.UniversalApiContract"
// - To system metadata assemblies, e.g. "Windows.Foundation"
// - To partial namespace assemblies, e.g. "Windows.Foundation.Collections"
// - To union metadata assemblies, e.g. "Windows"
// Since WinRT does not support overloading by full name and the "Windows." namespace is reserved,
// we can safely resolve to a previously loaded type by its full name only, ignoring the assembly identity.
if assemblyFlags.contains(AssemblyFlags.windowsRuntime), Self.isUWPAssemblyName(assemblyIdentity.name),
let namespace, namespace == "Windows" || namespace.starts(with: "Windows.") {
let fullName = "\(namespace).\(name)"
if let typeDefinition = uwpTypes[fullName] { return typeDefinition }
if assemblyFlags?.contains(AssemblyFlags.windowsRuntime) != false, Self.isUWPAssemblyName(assemblyIdentity.name),
let namespace = name.namespace, namespace == "Windows" || namespace.starts(with: "Windows.") {
if let typeDefinition = uwpTypes[name.fullName] { return typeDefinition }
}

let assembly = try load(identity: assemblyIdentity, flags: assemblyFlags)
guard let typeDefinition = try assembly.resolveTypeDefinition(namespace: namespace, name: name) else {
let fullName = namespace.map { "\($0).\(name)" } ?? name
throw AssemblyLoadError.notFound(message: "Type '\(fullName)' not found in assembly '\(assembly.name)'")
guard let typeDefinition = try assembly.resolveTypeDefinition(name: name) else {
throw AssemblyLoadError.notFound(message: "Type '\(name)' not found in assembly '\(assembly.name)'")
}

return typeDefinition
Expand Down
10 changes: 6 additions & 4 deletions Sources/DotNetMetadata/Attribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,21 @@ public final class Attribute {
case .constant(let constant): return .constant(constant)

case let .type(fullName, assemblyIdentity):
let assembly: Assembly
let typeDefinition: TypeDefinition
if let assemblyIdentity {
assembly = try self.assembly.context.load(identity: assemblyIdentity)
typeDefinition = try self.assembly.context.resolveType(
assembly: assemblyIdentity, assemblyFlags: nil,
name: TypeName(fullName: fullName))
}
else {
// TODO: Fallback to mscorlib
// §II.23.3:
// > If the assembly name is omitted, the CLI looks first in the current assembly,
// > and then in the system library (mscorlib); in these two special cases,
// > it is permitted to omit the assembly-name, version, culture and public-key-token.
assembly = self.assembly
typeDefinition = try self.assembly.resolveTypeDefinition(fullName: fullName)!
}
return .type(definition: try assembly.resolveTypeDefinition(fullName: fullName)!)
return .type(definition: typeDefinition)

case .array(let elems): return .array(try elems.map(resolve))
case .boxed(_): fatalError("Not implemented: boxed custom attribute arguments")
Expand Down
2 changes: 1 addition & 1 deletion Sources/DotNetMetadata/BoundType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ extension BoundTypeOf: CustomStringConvertible {
result += "."
}

result += definition.nameWithoutGenericSuffix
result += definition.nameWithoutGenericArity

if genericArgs.count > 0 {
result += "<"
Expand Down
5 changes: 2 additions & 3 deletions Sources/DotNetMetadata/ExportedType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public final class ExportedType {

public private(set) lazy var fullName: String = {
// TODO: Support nested exported types
makeFullTypeName(namespace: namespace, name: name)
TypeName.toFullName(namespace: namespace, shortName: name)
}()

private var cachedDefinition: TypeDefinition?
Expand All @@ -44,8 +44,7 @@ public final class ExportedType {
return try context.resolveType(
assembly: assemblyReference.identity,
assemblyFlags: assemblyReference.flags,
namespace: namespace,
name: name)
name: TypeName(namespace: namespace, shortName: name))
default:
fatalError("Not implemented: \(#function)")
}
Expand Down
32 changes: 3 additions & 29 deletions Sources/DotNetMetadata/TypeDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import DotNetMetadataFormat
public class TypeDefinition: CustomDebugStringConvertible, Attributable {
internal typealias Kind = TypeDefinitionKind

public static let nestedTypeSeparator: Character = "/"
public static let genericParamCountSeparator: Character = "`"

public private(set) weak var assembly: Assembly!
public let tableRowIndex: TableRowIndex // In TypeDef table

Expand Down Expand Up @@ -41,12 +38,7 @@ public class TypeDefinition: CustomDebugStringConvertible, Attributable {
public var isReferenceType: Bool { kind.isReferenceType }

public var name: String { moduleFile.resolve(tableRow.typeName) }

public var nameWithoutGenericSuffix: String {
let name = name
guard let index = name.firstIndex(of: Self.genericParamCountSeparator) else { return name }
return String(name[..<index])
}
public var nameWithoutGenericArity: String { TypeName.trimGenericArity(name) }

public var namespace: String? {
let tableRow = tableRow
Expand All @@ -61,9 +53,9 @@ public class TypeDefinition: CustomDebugStringConvertible, Attributable {
cachedFullName.lazyInit {
if let enclosingType = try? enclosingType {
assert(namespace == nil)
return "\(enclosingType.fullName)\(Self.nestedTypeSeparator)\(name)"
return "\(enclosingType.fullName)\(TypeName.nestedTypeSeparator)\(name)"
}
return makeFullTypeName(namespace: namespace, name: name)
return TypeName.toFullName(namespace: namespace, shortName: name)
}
} }

Expand Down Expand Up @@ -316,21 +308,3 @@ extension TypeDefinition: Hashable {
public func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) }
public static func == (lhs: TypeDefinition, rhs: TypeDefinition) -> Bool { lhs === rhs }
}

public func makeFullTypeName(namespace: String?, name: String) -> String {
if let namespace { return "\(namespace).\(name)" }
else { return name }
}

public func makeFullTypeName(namespace: String?, enclosingName: String, nestedNames: [String]) -> String {
var result: String
if let namespace { result = "\(namespace).\(enclosingName)" }
else { result = enclosingName }

for nestedName in nestedNames {
result.append(TypeDefinition.nestedTypeSeparator)
result += nestedName
}

return result
}
70 changes: 70 additions & 0 deletions Sources/DotNetMetadata/TypeName.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/// Represents the name of a .NET Type, including its namespace and nested type names.
public struct TypeName: Hashable, CustomStringConvertible {
public static let namespaceSeparator: Character = "."
public static let nestedTypeSeparator: Character = "/"
public static let genericAritySeparator: Character = "`"

public let namespace: String?
/// The short type name and any nested type names.
public let shortNames: [String] // Invariant: non-empty
// TODO: Splitoff generic arity?

public init(namespace: String?, shortNames: [String]) {
assert(!shortNames.isEmpty)
self.namespace = namespace
self.shortNames = shortNames
}

public init(namespace: String?, shortName: String) {
assert(!shortName.contains(Self.namespaceSeparator))
assert(!shortName.contains(Self.nestedTypeSeparator))
self.namespace = namespace
self.shortNames = [shortName]
}

public init(namespace: String?, outermostShortName: String, nestedNames: [String]) {
self.init(namespace: namespace, shortNames: [outermostShortName] + nestedNames)
}

public init(fullName: String) {
let namespaceEnd = fullName.lastIndex(of: Self.namespaceSeparator)
let shortNamesStart = namespaceEnd.map { fullName.index(after: $0) } ?? fullName.startIndex
self.init(
namespace: namespaceEnd.map { String(fullName[...$0]) },
shortNames: fullName[shortNamesStart...].split(separator: Self.nestedTypeSeparator).map(String.init))
}

public var outermostShortName: String { shortNames.first! }
public var nestedNames: ArraySlice<String> { shortNames.dropFirst() }

public var fullName: String {
var result: String = ""
if let namespace {
result = namespace
result.append(TypeName.namespaceSeparator)
}

for (index, shortName) in shortNames.enumerated() {
if index > 0 { result.append(Self.nestedTypeSeparator) }
result += shortName
}

return result
}

public var description: String { fullName }

public static func toFullName(namespace: String?, shortName: String) -> String {
if let namespace { return "\(namespace)\(namespaceSeparator)\(shortName)" }
else { return shortName }
}

public static func toFullName(namespace: String?, outermostShortName: String, nestedNames: [String]) -> String {
Self(namespace: namespace, outermostShortName: outermostShortName, nestedNames: nestedNames).fullName
}

public static func trimGenericArity(_ name: String) -> String {
guard let genericAritySeparatorIndex = name.lastIndex(of: genericAritySeparator) else { return name }
return String(name[..<genericAritySeparatorIndex])
}
}
4 changes: 2 additions & 2 deletions Sources/DotNetXMLDocs/DocumentationTypeNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ public enum DocumentationTypeNode: Hashable {
extension DocumentationTypeNode {
public static func bound(
namespace: String? = nil,
nameWithoutGenericSuffix: String,
nameWithoutGenericArity: String,
genericity: DocumentationTypeReference.Genericity = .bound([])) -> Self {
.bound(DocumentationTypeReference(
namespace: namespace,
nameWithoutGenericSuffix: nameWithoutGenericSuffix,
nameWithoutGenericArity: nameWithoutGenericArity,
genericity: genericity))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extension DocumentationTypeReference {

return Self(
namespace: identifier.namespace.map(String.init),
nameWithoutGenericSuffix: String(identifier.name),
nameWithoutGenericArity: String(identifier.name),
genericity: genericity)
}

Expand Down
14 changes: 7 additions & 7 deletions Sources/DotNetXMLDocs/DocumentationTypeReference.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
public struct DocumentationTypeReference: Hashable {
public var namespace: String?
public var nameWithoutGenericSuffix: String
public var nameWithoutGenericArity: String
public var genericity: Genericity

public init(namespace: String? = nil, nameWithoutGenericSuffix: String, genericity: Genericity = .bound([])) {
public init(namespace: String? = nil, nameWithoutGenericArity: String, genericity: Genericity = .bound([])) {
self.namespace = namespace
self.nameWithoutGenericSuffix = nameWithoutGenericSuffix
self.nameWithoutGenericArity = nameWithoutGenericArity
self.genericity = genericity
}

public init(namespace: String? = nil, nameWithoutGenericSuffix: String, genericArity: Int) {
public init(namespace: String? = nil, nameWithoutGenericArity: String, genericArity: Int) {
self.init(
namespace: namespace,
nameWithoutGenericSuffix: nameWithoutGenericSuffix,
nameWithoutGenericArity: nameWithoutGenericArity,
genericity: genericArity == 0 ? .bound([]) : .unbound(arity: genericArity))
}

public init(namespace: String? = nil, nameWithoutGenericSuffix: String, genericArgs: [DocumentationTypeNode]) {
public init(namespace: String? = nil, nameWithoutGenericArity: String, genericArgs: [DocumentationTypeNode]) {
self.init(
namespace: namespace,
nameWithoutGenericSuffix: nameWithoutGenericSuffix,
nameWithoutGenericArity: nameWithoutGenericArity,
genericity: .bound(genericArgs))
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/DotNetXMLDocs/MemberDocumentationKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public enum MemberDocumentationKey: Hashable {
extension MemberDocumentationKey {
public static func type(
namespace: String? = nil,
nameWithoutGenericSuffix: String,
nameWithoutGenericArity: String,
genericity: DocumentationTypeReference.Genericity = .bound([])) -> Self {
.type(DocumentationTypeReference(
namespace: namespace,
nameWithoutGenericSuffix: nameWithoutGenericSuffix,
nameWithoutGenericArity: nameWithoutGenericArity,
genericity: genericity))
}
}
4 changes: 2 additions & 2 deletions Tests/DotNetXMLDocs/AssemblyDocumentationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class AssemblyDocumentationTests: XCTestCase {
XCTAssertEqual(assemblyDocumentation.assemblyName, "AssemblyName")
XCTAssertEqual(assemblyDocumentation.members.count, 2)

XCTAssertNotNil(assemblyDocumentation.members[.type(nameWithoutGenericSuffix: "TypeA")])
XCTAssertNotNil(assemblyDocumentation.members[.type(nameWithoutGenericSuffix: "TypeB")])
XCTAssertNotNil(assemblyDocumentation.members[.type(nameWithoutGenericArity: "TypeA")])
XCTAssertNotNil(assemblyDocumentation.members[.type(nameWithoutGenericArity: "TypeB")])
}
}
6 changes: 3 additions & 3 deletions Tests/DotNetXMLDocs/DocumentationTypeNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ final class DocumentationTypeNodeTests: XCTestCase {
func testParseBound() throws {
XCTAssertEqual(
try DocumentationTypeNode(parsing: "Name"),
.bound(nameWithoutGenericSuffix: "Name"))
.bound(nameWithoutGenericArity: "Name"))
}

func testParseArray() throws {
XCTAssertEqual(
try DocumentationTypeNode(parsing: "Name[]"),
.array(of: .bound(nameWithoutGenericSuffix: "Name")))
.array(of: .bound(nameWithoutGenericArity: "Name")))
}

func testParsePointer() throws {
XCTAssertEqual(
try DocumentationTypeNode(parsing: "Name*"),
.pointer(to: .bound(nameWithoutGenericSuffix: "Name")))
.pointer(to: .bound(nameWithoutGenericArity: "Name")))
}

func testParseGenericParam() throws {
Expand Down
8 changes: 4 additions & 4 deletions Tests/DotNetXMLDocs/DocumentationTypeReferenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ final class DocumentationTypeReferenceTests: XCTestCase {
func testParseNonGeneric() throws {
XCTAssertEqual(
try DocumentationTypeReference(parsing: "Name"),
.init(nameWithoutGenericSuffix: "Name"))
.init(nameWithoutGenericArity: "Name"))
}

func testParseUnboundGeneric() throws {
XCTAssertEqual(
try DocumentationTypeReference(parsing: "Name`1"),
.init(nameWithoutGenericSuffix: "Name", genericity: .unbound(arity: 1)))
.init(nameWithoutGenericArity: "Name", genericity: .unbound(arity: 1)))
}

func testParseBoundGeneric() throws {
XCTAssertEqual(
try DocumentationTypeReference(parsing: "Name`1{Name2}"),
.init(nameWithoutGenericSuffix: "Name", genericArgs: [ .bound(nameWithoutGenericSuffix: "Name2") ]))
.init(nameWithoutGenericArity: "Name", genericArgs: [ .bound(nameWithoutGenericArity: "Name2") ]))
}

func testParseNamespaced() throws {
XCTAssertEqual(
try DocumentationTypeReference(parsing: "Namespace.Name"),
.init(namespace: "Namespace", nameWithoutGenericSuffix: "Name"))
.init(namespace: "Namespace", nameWithoutGenericArity: "Name"))
}
}
Loading

0 comments on commit ba90c8a

Please sign in to comment.