Skip to content

[pigeon] Generated swift code throws compile time error on recursive types #145175

Closed
flutter/packages
#6372

Description

@LouiseHsu

Given an input of

  SKPaymentTransactionMessage({
    required this.payment,
    required this.transactionState,
    this.originalTransaction,
    this.transactionTimeStamp,
    this.transactionIdentifier,
    this.error,
  });

  final SKPaymentMessage payment;
  final SKPaymentTransactionStateMessage transactionState;
  final SKPaymentTransactionMessage? originalTransaction;
  final double? transactionTimeStamp;
  final String? transactionIdentifier;
  final SKErrorMessage? error;
}

Pigeon generates

struct SKPaymentTransactionMessage {
  var payment: SKPaymentMessage
  var transactionState: SKPaymentTransactionStateMessage
  var originalTransaction: SKPaymentTransactionMessage? = nil
  var transactionTimeStamp: Double? = nil
  var transactionIdentifier: String? = nil
  var error: SKErrorMessage? = nil
  \\ ...

However, the swift compiler will complain that
Value type 'SKPaymentTransactionMessage' cannot have a stored property that recursively contains it

I ran into this issue while I was trying to migrate in_app_purchase_storekit from obj-c to swift, and didn't have issues with recursive types when generating obj-c.

cc @stuartmorgan

Activity

  1. added
    p: pigeonrelated to pigeon messaging codegen tool
    packageflutter/packages repository. See also p: labels.
    on Mar 14, 2024
  2. stuartmorgan-g commented on Mar 14, 2024

    @stuartmorgan-g
    Contributor

    From what I found in initial investigation, it looks this this is a fundamental limitation with struct in Swift (since it apparently uses inline storage of fields, so can't support recursion), and the standard approach seems to be to use classes instead when recursion is needed.

    We can look at either switching to classes entirely, or if the ergonomics are similar enough between the two, only using classes when we detect this condition.

    /cc @hellohuanlin for any input on idiomatic Swift (e.g., if mixing structs and classes would be an issue from a consistency standpoint).

  3. hellohuanlin commented on Mar 14, 2024

    @hellohuanlin
    Contributor

    This is a limitation of struct that it doesn't support "indirect" heap storage. Interestingly indirect has been supported by enums for a long time, but not structs.

    An easy workaround is to use a class wrapper for the indirection:

    struct SKPaymentTransactionMessage {
      var originalTransaction: MyWrapper?
    }
    class MyWrapper { // <- ref type
      let wrappee: SKPaymentTransactionMessage
    }
    

    Alternatively, we can also use enum as a wrapper, something like:

    struct SKPaymentTransactionMessage {
      var originalTransaction: MyWrapper?
    }
    
    indirect enum MyWrapper { // <- indirect keyword
      case wrappee(wrappee: SKPaymentTransactionMessage)
    }
    

    If we use Swift 5.1+ (do we?), we can also use "property wrapper" to make the above more concise.

  4. stuartmorgan-g commented on Mar 14, 2024

    @stuartmorgan-g
    Contributor

    What downsides would there be to just making the type itself a class instead of a struct when this happens, instead of adding a layer of indirection that clients need to deal with?

  5. hellohuanlin commented on Mar 14, 2024

    @hellohuanlin
    Contributor

    What downsides would there be to just making the type itself a class instead of a struct when this happens, instead of adding a layer of indirection that clients need to deal with?

    I was thinking about the workaround without changing pigeon. Otherwise I agree it's better to just use a class so clients don't have to deal with this.

    The downside is that we lose the value semantics and some nice features (e.g. immutability & copy-on-write, auto synthesized methods);

    The upside is that classes are less costly. Structs/enums had major performance overhead at early version of swift. It has improved a lot since then but still come with extra overhead.

  6. added
    P1High-priority issues at the top of the work list
    on Mar 21, 2024
  7. stuartmorgan-g commented on Mar 21, 2024

    @stuartmorgan-g
    Contributor

    @tarrinneal FYI. Marking as P1 since I don't see a good project-level workaround. We can try using class only when necessary and see how inconsistent it is, and decide from there whether to just change everything.

  8. LouiseHsu commented on Mar 21, 2024

    @LouiseHsu
    ContributorAuthor

    To piggy back on to this issue, making the structs classes would also allow for objc interop, since the @objc attribute can only be added to classes that inherit from NSObject.

  9. self-assigned this
    on Mar 21, 2024
  10. hellohuanlin commented on Mar 21, 2024

    @hellohuanlin
    Contributor

    We can try using class only when necessary and see how inconsistent it is

    Note that the same type could be nested at a deeper level, so this probably requires some sort of traversal (feels like an interesting leetcode problem).

          A
        / | \
       B  C  D
      / \
      E A (here)
    

    If this is turns out hard to do, I think it's fine to just use class for everything.

  11. added a commit that references this issue on Mar 26, 2024
  12. github-actions commented on Apr 9, 2024

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  13. locked as resolved and limited conversation to collaborators on Apr 9, 2024
  14. added a commit that references this issue on Jun 10, 2026
  15. added a commit that references this issue on Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listp: pigeonrelated to pigeon messaging codegen toolpackageflutter/packages repository. See also p: labels.team-ecosystemOwned by Ecosystem teamtriaged-ecosystemTriaged by Ecosystem team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions