Skip to content

JNI: Java overloads for trailing default arguments - #18

Open
piercifani wants to merge 4 commits into
jni/13-java-enumsfrom
jni/14-default-arguments
Open

piercifani wants to merge 4 commits into
jni/13-java-enumsfrom
jni/14-default-arguments

Conversation

@piercifani

@piercifani piercifani commented Nov 3, 2026 •

Copy link
Copy Markdown

Stack 14/21 · base: jni/13-java-enums · previous: #17 · next: #19 (jni/15-nullable-optionals)

Problem

Swift default arguments didn't reach Java. func greeting(name: String = "world", excited: Bool = false) became only greeting(String, boolean), so Java and Kotlin callers had to pass every argument and repeat the Swift defaults by hand.

Changes

Each function and initializer now also gets one overload for each trailing defaulted parameter it can be called without. For the example: greeting(String, boolean), greeting(String) and greeting(). This works like the existing expansion of variadic parameters: AnalysisResult.expandDefaultArgumentOverloads() runs right after expandVariadicOverloads in the JNI generator.

  • No default value is translated. An overload's Swift thunk calls the function without the left-out arguments (greeting(name: …), greeting()), so Swift supplies its own defaults, whatever their expressions are.
  • Only trailing defaults. Java overloads differ by arity, so f(a: Int = 0, b: Int) gets no overload.
  • Declared functions win. An overload is skipped when a function the module declares already has that name and parameter types: f(a: Int64 = 0) next to f() produces only the two declared ones.
  • Scope: JNI mode only (FFM output is unchanged), and protocols are left alone because their requirements can't have default arguments.
  • Closures with defaults, as in a view model's Callbacks, need three more things:
    • Overloads share their closure parameters' Swift @JavaInterface wrappers and Java helper class, so each is printed once.
    • A method whose result may need a SwiftArena (initializers, and results other than primitives, strings and Void) also gets an overload ending in SwiftArena, and a Kotlin lambda converts to it. So init(onOpen, SwiftArena) would be ambiguous with init(onOpen, onClose). Such an overload is left out when the next parameter is a closure and the longer overload exists. Callbacks(onOpen:onClose:) gets init(onOpen, onClose) and init().
    • Java helper classes get a private constructor, so Kotlin's Callbacks.init() isn't ambiguous with constructing the helper class Callbacks.init. Existing tests that spell out a helper class now include it.

Overloads are only added, so existing callers are unaffected.

Kotlin in this PR

DefaultArgumentsTest.kt with Receipts.swift:

// public static func format(_ amount: Int64, currency: String = "USD", showsSign: Bool = false) -> String
Receipts.format(5)                 // "5 USD"
Receipts.format(5, "EUR")          // "5 EUR"
Receipts.format(5, "EUR", true)    // "+5 EUR"

ReceiptPrinter has two defaulted callbacks: Kotlin passes both lambdas, ReceiptPrinter.init({ lines += it }, { failed = true }), or neither, ReceiptPrinter.init().

Kotlin named arguments still don't apply to Java methods, so only trailing arguments can be left out.

Why Skip needs it

Skip turns Swift default arguments into Kotlin default parameters, and app Kotlin code leaves them out. Without these overloads, every such call would have to spell out the Swift default.

Testing

  • Kotlin sample: ./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:test passes (39 tests).
  • New JNIDefaultArgumentsTests (7 tests): the Java overloads and their natives; the Swift calls leaving the arguments out; initializer overloads; declared overloads win, checked on the expanded analysis; no overload for a non-trailing default; closure overloads that would be ambiguous with an arena are left out (but kept for a Void result); closure wrappers and helper classes printed once.
  • JNI sample: DefaultArgumentsTest calls greeting(), greeting("Ana") and greeting("Ana", true).
  • Full swift test --disable-experimental-prebuilts --disable-sandbox (668 tests) and ./gradlew :Samples:SwiftJavaExtractJNISampleApp:test (293 tests) pass.
  • swift format lint --strict on changed files; git diff --check.

Not in this PR

  • Optionals as nullable types, the other half of the repro's Kotlin-friendliness gap, comes in the next PR.
  • Defaults on Java-implemented protocol methods, and FFM mode.

🤖 Generated with Claude Code

piercifani and others added 2 commits November 3, 2026 18:25
Java and Kotlin callers had to pass every argument, including those with
Swift defaults.

Each function and initializer now gets an overload per trailing defaulted
parameter it can be called without, like the existing variadic expansion.
The overload's Swift thunk calls the function without those arguments, so
Swift supplies its own defaults; no default expression is translated. An
overload is skipped when a declared function already has that name and
parameter types. JNI mode only; protocols are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
piercifani and others added 2 commits November 3, 2026 18:47
…Kotlin

Found with a view model's `Callbacks` whose closures all have defaults:

- Overloads share their closure parameters' Swift `@JavaInterface`
  wrappers and Java helper class; each is now printed once.
- With a result that may take a `SwiftArena`, `init(onOpen, SwiftArena)`
  was ambiguous with `init(onOpen, onClose)` for Kotlin lambdas, which
  convert to `SwiftArena`. Such an overload is left out when the next
  parameter is a closure and the longer overload exists.
- A Java helper class gets a private constructor, so Kotlin's `T.init()`
  isn't ambiguous with constructing the helper class `T.init`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant