Skip to content

[pigeon] Reject unsupported native interop features and fix generated code that doesn't compile - #13153

Merged
auto-submit[bot] merged 5 commits into
flutter:mainfrom
tarrinneal:handle-breaking-code-options
Oct 9, 2026
Merged

auto-submit[bot] merged 5 commits into
flutter:mainfrom
tarrinneal:handle-breaking-code-options

Conversation

@tarrinneal

@tarrinneal tarrinneal commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Pigeon accepted several combinations of swift_use_ffi or kotlin_use_jni with other features and options that generated code that doesn't compile. One combination crashed pigeon. flutter/flutter#193881 reported two of them: event channels and ProxyApis with Swift FFI.

To find the rest, I generated every repo pigeon input and a set of edge-case inputs with each interop option. I then type-checked the Swift, compiled the Kotlin and analyzed the Dart. That found 19 broken combinations, and fixing them found a few more.

This PR adds a validation error where native interop has no implementation yet, and fixes the generators for everything else. It also adds a test API to the test plugin that uses the names JNIgen renames, so the integration tests check pigeon's copy of JNIgen's renaming rules at runtime.

New validation errors. Apart from event channels and ProxyApis with Kotlin JNI, none of these combinations ever produced code that compiled.

  • Swift FFI:
    • Event channels, ProxyApis and sealed classes. The error says to move them to a separate pigeon file that doesn't use swift_use_ffi.
    • Data class fields that conflict with NSObject members, because FFI bridge classes extend NSObject. Examples are hash, isProxy and superclass, plus names that ffigen renames, such as alloc or isEqual.
    • HostApi parameters named wrappedError. ffigen would generate two wrappedError: named parameters.
    • includeErrorClass: false. swiftgen compiles each Swift file on its own, so the file must declare its error class. Give each file a different errorClassName instead.
  • Kotlin JNI: Event channels, ProxyApis and sealed classes, the same as Swift FFI. The error says to move them to a separate pigeon file that doesn't use kotlin_use_jni.
    • Sealed classes never worked. The Kotlin compiles, but the generated Dart has no JNI conversion for them.
    • Event channels and ProxyApis did compile, because the Kotlin file falls back to channel code for them. They're rejected so that both interop options support the same features until these work over native interop. A file that generated before can now fail validation, but native interop is still marked experimental.
  • Dart: API method names that conflict with generated native interop members:
    • HostApi: getInstance and createWithNativeInteropApi.
    • FlutterApi: implement, and with JNI also dartApi and register.

Generator fixes

  • Swift (FFI):
    • The bridge for a class without fields didn't compile because its init() was missing override.
    • @asyncCallback host methods: the bridge awaited a callback-style method. It now wraps the call in withCheckedThrowingContinuation.
    • @SwiftFunction overloads that differ only in nullability were ambiguous. The bridge now declares the result type.
    • An API method named type broke error handling in every method of that API, because type(of:) resolved to the method. The bridge now uses Swift.type(of:).
    • Parameters that shared a name with one of the bridge's own parameters, such as a FlutterApi parameter named error, collided with it. Internal parameter names now end in Arg. Selectors don't change.
    • Two FFI files in the same module both declared _PigeonFfiCodec. It's now private.
  • Dart:
    • Pigeon crashed with Bad state: No element when a class without fields was used with Swift FFI.

    • JNI calls used the Kotlin member names, but JNIgen changes some of them:

      • It renames members that clash with its own, such as type, release, use and isNull.
      • It turns getX(), isX() and setX() into properties.
      • It turns is-prefixed data class properties that aren't Boolean into methods.

      A new _JniBindingNamer mirrors the JNIgen 1.0.1 renamer for data class fields, HostApi methods and FlutterApi overrides. It replaces _getJniFieldName, which handled only a few field names.

    • FFI calls assumed that Swift's Objective-C selectors always join the method name and the first argument label with With. Swift leaves out With when the method name ends with a preposition or the label starts with one. For example, signIn(value:) becomes signInValue: and init(forKey:) becomes initForKey:. Dart now applies the same rule to host API calls, FlutterApi callbacks and data class bridge initializers. It uses the preposition list from Swift's PartsOfSpeech.def and Swift's camel-case word splitting.

    • Generated local variables shadowed parameters with the same name, such as res, error, completer and response. Generated locals now use the pigeonVar_ prefix, and callback parameters use arg_.

    • Lists and maps of ProxyApis caused the interop codecs to contain the literal text There is something wrong, a type is not classified. The codecs now skip those types.

    • The class-name prefix for FFI types didn't match the Swift one in three cases:

      • The Swift file name wasn't UpperCamelCase.
      • fileSpecificClassNameComponent wasn't UpperCamelCase.
      • SwiftOptions.fileSpecificClassNameComponent was set.

      Dart now uses the same precedence and casing as the Swift generator.

    • Test files (dartTestOut) contained native interop code that referenced bindings they don't import. They're now generated with method channels only, which is all their mock handlers use.

  • Docs: The includeErrorClass docs on SwiftOptions and KotlinOptions explain the native interop requirements.

Test plugin

native_interop_tests.dart adds NativeInteropReservedNames, NativeInteropReservedNamesHostApi and NativeInteropReservedNamesFlutterApi. Their names cover each JNIgen renaming rule:

  • members that clash with JNIgen's own, such as release, use, type, isNull, reference and jClass;
  • getters, setters and is methods that JNIgen turns into properties, including getDefault, which becomes a Dart keyword;
  • is fields that aren't bool, which JNIgen turns into methods;
  • an @async method, which JNIgen keeps as a method because it's a Kotlin suspend function;
  • a FlutterApi method named implementIn, which clashes with JNIgen's implementIn.

The Kotlin and Swift test plugins implement both APIs, and new integration tests call every method and round-trip the data class. The tests use true for isNull and isReleased, so they fail if the generated code calls JObject's own members instead, which return false. This API is also how I found the Swift selector bug above: implementIn ends with a preposition.

The test plugin and the native interop example are regenerated. Apart from the new test API, their diffs contain only:

  • the renames above;
  • private on _PigeonFfiCodec;
  • Swift.type(of:);
  • the matching ffigen bindings and Objective-C header.

Selectors and public APIs don't change.

Testing

  • New unit tests: 12 for validation, 6 for the Dart generator and 4 for the Swift generator. All 581 pigeon unit tests pass.
  • The Android (emulator) and macOS integration suites pass, including the new reserved-name tests: 674 tests on Android, and 673 on macOS with 1 existing skip. I didn't run iOS locally.
  • I compiled Kotlin that uses every JNIgen-reserved name and ran JNIgen 1.0.1 on it. The generated Dart analyzes against those bindings with no errors.
  • I checked Swift's selector rule with swiftc, for every preposition and for names with underscores, digits and acronyms.
  • I re-ran the full probe. Validation rejects exactly the unsupported combinations, and the generated Swift for every other case type-checks with swiftc.

Fixes flutter/flutter#193881

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates Pigeon to version 29.0.7, adding validation checks for unsupported Swift FFI and Kotlin JNI features, such as event channels, ProxyApis, sealed classes, and conflicting member or parameter names. It also fixes compilation issues in Swift FFI and Dart native interop, including parameter shadowing, empty classes, and JNIgen member renaming, and adds corresponding unit tests. There are no review comments, and no feedback is provided.

@dkwingsmt dkwingsmt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. I tried my best to understand the PR and I don't see any major flaws.

The only thing I'm worried about is relying on the exact implementation of JNIgen and hardcoded declaredFirst, which might mismatch the versions used by users in the future. Although, they might be stable for a long time, which is enough reason for this PR to land now.

Also,

A file that generated before can now fail validation, but native interop is still marked experimental.

Should we note this in the changelog?

@tarrinneal

tarrinneal commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

LGTM. I tried my best to understand the PR and I don't see any major flaws.

The only thing I'm worried about is relying on the exact implementation of JNIgen and hardcoded declaredFirst, which might mismatch the versions used by users in the future. Although, they might be stable for a long time, which is enough reason for this PR to land now.

Also,

A file that generated before can now fail validation, but native interop is still marked experimental.

Should we note this in the changelog?

I think realistically, those files that passed validation wouldn't have been functional.

As for the mimicking of jnigen behavior, until jnigen allows access to the methods used to make these outputs, I can't really do anything else. I'm planing on requesting access to them when @liamappelbe is back from vacation.

@tarrinneal tarrinneal added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 9, 2026
@auto-submit
auto-submit Bot merged commit d8d9e97 into flutter:main Oct 9, 2026
13 checks passed
jmagman pushed a commit to jmagman/flutter that referenced this pull request Oct 9, 2026
flutter/packages@54706bd...d8d9e97

2026-10-09 [email protected] [pigeon] Reject unsupported native
interop features and fix generated code that doesn't compile
(flutter/packages#13153)
2026-10-09 [email protected] [material_ui] Fix
ExpansionTile WidgetSpan semantics assertion (flutter/packages#12765)
2026-10-08 [email protected] [in_app_purchase_storekit] Complete
StoreKit 2 `finish` when the transaction is not found
(flutter/packages#13037)
2026-10-08 [email protected] [video_player] Fix crash when
videoTrack has zero-duration minFrameDuration (flutter/packages#12796)
2026-10-08 [email protected]
[google_maps_flutter_platform_interface] Add map background color
configuration (flutter/packages#13014)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
flutter/packages@54706bd...d8d9e97

2026-10-09 [email protected] [pigeon] Reject unsupported native
interop features and fix generated code that doesn't compile
(flutter/packages#13153)
2026-10-09 [email protected] [material_ui] Fix
ExpansionTile WidgetSpan semantics assertion (flutter/packages#12765)
2026-10-08 [email protected] [in_app_purchase_storekit] Complete
StoreKit 2 `finish` when the transaction is not found
(flutter/packages#13037)
2026-10-08 [email protected] [video_player] Fix crash when
videoTrack has zero-duration minFrameDuration (flutter/packages#12796)
2026-10-08 [email protected]
[google_maps_flutter_platform_interface] Add map background color
configuration (flutter/packages#13014)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD p: pigeon platform-android platform-ios platform-macos

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[pigeon] Swift FFI (swift_use_ffi) generates code that doesn't compile for files with event channels or ProxyApis

2 participants