Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

public typealias Amount = Double

public struct TypealiasUser {
public var amount: Amount

public init(amount: Amount) {
self.amount = amount
}

public func doubled() -> Amount {
amount * 2
}
}

public func makeAmount(_ value: Amount) -> Amount {
value
}

// Generic typealias used with a use-site argument. The alias's generic
// parameter `T` is substituted at the use site so `Maybe<Int64>` resolves
// to `Optional<Int64>`, which is `java.lang.OptionalLong` in Java.
public typealias Maybe<T> = T?

public func unwrapOrZero(_ value: Maybe<Int64>) -> Int64 {
value ?? 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.ffm.AllocatingSwiftArena;

import java.util.OptionalLong;

import static org.junit.jupiter.api.Assertions.*;

public class TypealiasUserTest {
@Test
void plainTypealiasResolvesStructMembers() {
try (var arena = AllocatingSwiftArena.ofConfined()) {
var user = TypealiasUser.init(2.5, arena);
assertEquals(2.5, user.getAmount(), 0.0);
assertEquals(5.0, user.doubled(), 0.0);

user.setAmount(7.0);
assertEquals(7.0, user.getAmount(), 0.0);
}
}

@Test
void freeFunctionThroughAliasIsExported() {
assertEquals(42.0, MySwiftLibrary.makeAmount(42.0), 0.0);
}

@Test
void genericTypeAliasSubstitutesUseSiteArguments() {
// `Maybe<Int64>` substitutes T -> Int64, resolving to Optional<Int64>,
// which is then mapped to java.util.OptionalLong.
assertEquals(0L, MySwiftLibrary.unwrapOrZero(OptionalLong.empty()));
assertEquals(123L, MySwiftLibrary.unwrapOrZero(OptionalLong.of(123L)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

public typealias Amount = Double

public struct TypealiasUser {
public var amount: Amount

public init(amount: Amount) {
self.amount = amount
}

public func doubled() -> Amount {
amount * 2
}
}

public func makeAmount(_ value: Amount) -> Amount {
value
}

// Generic typealias used with a use-site argument. The alias's generic
// parameter `T` is substituted at the use site so `Maybe<Int64>` resolves
// to `Optional<Int64>`, which is `java.lang.OptionalLong` in Java.
public typealias Maybe<T> = T?

public func unwrapOrZero(_ value: Maybe<Int64>) -> Int64 {
value ?? 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

package com.example.swift;

import org.junit.jupiter.api.Test;
import org.swift.swiftkit.core.SwiftArena;

import java.util.OptionalLong;

import static org.junit.jupiter.api.Assertions.*;

public class TypealiasUserTest {
@Test
void plainTypealiasResolvesStructMembers() {
try (var arena = SwiftArena.ofConfined()) {
var user = TypealiasUser.init(2.5, arena);
assertEquals(2.5, user.getAmount(), 0.0);
assertEquals(5.0, user.doubled(), 0.0);

user.setAmount(7.0);
assertEquals(7.0, user.getAmount(), 0.0);
}
}

@Test
void freeFunctionThroughAliasIsExported() {
assertEquals(42.0, MySwiftLibrary.makeAmount(42.0), 0.0);
}

@Test
void genericTypeAliasSubstitutesUseSiteArguments() {
// `Maybe<Int64>` substitutes T -> Int64, resolving to Optional<Int64>,
// which is then mapped to java.util.OptionalLong.
assertEquals(0L, MySwiftLibrary.unwrapOrZero(OptionalLong.empty()));
assertEquals(123L, MySwiftLibrary.unwrapOrZero(OptionalLong.of(123L)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol {
/// The top-level nominal types, found by name.
var topLevelTypes: [String: SwiftNominalTypeDeclaration] = [:]

/// The top-level typealias declarations, found by name.
var topLevelTypeAliases: [String: SwiftTypeAliasDeclaration] = [:]

/// The nested types defined within this module. The map itself is indexed by the
/// identifier of the nominal type declaration, and each entry is a map from the nested
/// type name to the nominal type declaration.
Expand All @@ -38,6 +41,11 @@ struct SwiftModuleSymbolTable: SwiftSymbolTableProtocol {
topLevelTypes[name]
}

/// Look for a top-level typealias with the given name.
func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? {
topLevelTypeAliases[name]
}

// Look for a nested type with the given name.
func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration? {
nestedTypes[parent]?[name]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,24 @@ package class SwiftGenericParameterDeclaration: SwiftTypeDeclaration {
}
}

/// A plain typealias will resolve as the right hand type in generated code.
///
/// A typealias used as a specialization of a generic type will be emitted as
/// a new concrete type in the Java. This way we can specialize `FishBox` from
/// `Box<T>` by doing `typealias FishBox = Box<Fish>`.
package final class SwiftTypeAliasDeclaration: SwiftTypeDeclaration {
let syntax: TypeAliasDeclSyntax

init(
sourceFilePath: String,
moduleName: String,
node: TypeAliasDeclSyntax
) {
self.syntax = node
super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text)
}
}

extension SwiftTypeDeclaration: Equatable {
package static func == (lhs: SwiftTypeDeclaration, rhs: SwiftTypeDeclaration) -> Bool {
lhs === rhs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,29 @@ extension SwiftParsedModuleSymbolTableBuilder {
self.handle(extensionDecl: extensionNode, sourceFilePath: sourceFilePath)
} else if let ifConfigNode = decl.as(IfConfigDeclSyntax.self) {
self.handle(ifConfig: ifConfigNode, sourceFilePath: sourceFilePath)
} else if let typeAliasNode = decl.as(TypeAliasDeclSyntax.self) {
self.handle(typeAliasDecl: typeAliasNode, sourceFilePath: sourceFilePath)
}
}

mutating func handle(
typeAliasDecl node: TypeAliasDeclSyntax,
sourceFilePath: String
) {
let name = node.name.text
if symbolTable.topLevelTypeAliases[name] != nil
|| symbolTable.lookupTopLevelNominalType(name) != nil
{
log?.debug("Failed to add a typealias into symbol table: redeclaration; \(name)")
return
}
symbolTable.topLevelTypeAliases[name] = SwiftTypeAliasDeclaration(
sourceFilePath: sourceFilePath,
moduleName: moduleName,
node: node
)
}

/// Add a nominal type declaration and all of the nested types within it to the symbol
/// table.
mutating func handle(
Expand Down
18 changes: 18 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ package protocol SwiftSymbolTableProtocol {

// Look for a nested type with the given name.
func lookupNestedType(_ name: String, parent: SwiftNominalTypeDeclaration) -> SwiftNominalTypeDeclaration?

/// Look for a top-level typealias with the given name.
func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration?
}

extension SwiftSymbolTableProtocol {
Expand Down Expand Up @@ -178,6 +181,21 @@ extension SwiftSymbolTable: SwiftSymbolTableProtocol {

return nil
}

/// Look for a top-level typealias with the given name.
package func lookupTopLevelTypealias(_ name: String) -> SwiftTypeAliasDeclaration? {
if let parsedResult = parsedModule.lookupTopLevelTypealias(name) {
return parsedResult
}

for importedModule in prioritySortedImportedModules {
if let result = importedModule.lookupTopLevelTypealias(name) {
return result
}
}

return nil
}
}

extension SwiftSymbolTable {
Expand Down
72 changes: 72 additions & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,29 @@ extension SwiftType {
)
} else if let genericParamDecl = typeDecl as? SwiftGenericParameterDeclaration {
self = .genericParameter(genericParamDecl)
} else if let aliasDecl = typeDecl as? SwiftTypeAliasDeclaration {
let aliasGenericParams =
aliasDecl.syntax.genericParameterClause?.parameters.map { $0.name.text } ?? []
let useSiteArgs = genericArguments ?? []

// The alias's generic parameter count must match the use-site argument
// count. Treat any mismatch (including use-site args on a non-generic
// alias, or missing args on a generic alias) as unimplemented to fall
// through to silent drop.
guard aliasGenericParams.count == useSiteArgs.count else {
throw TypeTranslationError.unimplementedType(originalType)
}

let resolved = try lookupContext.resolve(typeAlias: aliasDecl)

if aliasGenericParams.isEmpty {
self = resolved
} else {
let substitutions = Dictionary(
uniqueKeysWithValues: zip(aliasGenericParams, useSiteArgs)
)
self = resolved.substituting(genericParameters: substitutions)
}
} else {
fatalError("unknown SwiftTypeDeclaration: \(type(of: typeDecl))")
}
Expand Down Expand Up @@ -494,6 +517,55 @@ extension SwiftType {
)
}

/// Substitute generic parameters *by name*.
///
/// This is used e.g. by typealiases like `typealias Ano<T> = Array<T>`,
/// so usages like `Ano<Int>` become `Array<Int>`.
func substituting(genericParameters substitutions: [String: SwiftType]) -> SwiftType {
guard !substitutions.isEmpty else { return self }

switch self {
case .nominal(let nominal):
return .nominal(
SwiftNominalType(
parent: nominal.parent,
sugarName: nominal.sugarName,
nominalTypeDecl: nominal.nominalTypeDecl,
genericArguments: nominal.genericArguments?.map {
$0.substituting(genericParameters: substitutions)
}
)
)
case .genericParameter(let decl):
return substitutions[decl.name] ?? self
case .function(var fn):
fn.parameters = fn.parameters.map { p in
var p = p
p.type = p.type.substituting(genericParameters: substitutions)
return p
}
fn.resultType = fn.resultType.substituting(genericParameters: substitutions)
return .function(fn)
case .metatype(let inner):
return .metatype(inner.substituting(genericParameters: substitutions))
case .tuple(let elements):
return .tuple(
elements.map {
SwiftTupleElement(
label: $0.label,
type: $0.type.substituting(genericParameters: substitutions)
)
}
)
case .existential(let inner):
return .existential(inner.substituting(genericParameters: substitutions))
case .opaque(let inner):
return .opaque(inner.substituting(genericParameters: substitutions))
case .composite(let types):
return .composite(types.map { $0.substituting(genericParameters: substitutions) })
}
}

/// Produce an expression that creates the metatype for this type in
/// Swift source code.
var metatypeReferenceExprSyntax: ExprSyntax {
Expand Down
Loading
Loading