You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[pigeon] Generated swift code throws compile time error on recursive types #145175
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.
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).
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.
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?
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.
@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.
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.
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.
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.
Activity
stuartmorgan-g commentedon Mar 14, 2024
From what I found in initial investigation, it looks this this is a fundamental limitation with
structin 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).
hellohuanlin commentedon Mar 14, 2024
This is a limitation of
structthat it doesn't support "indirect" heap storage. Interestinglyindirecthas been supported byenums for a long time, but notstructs.An easy workaround is to use a class wrapper for the indirection:
Alternatively, we can also use
enumas a wrapper, something like:If we use Swift 5.1+ (do we?), we can also use "property wrapper" to make the above more concise.
stuartmorgan-g commentedon Mar 14, 2024
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?
hellohuanlin commentedon Mar 14, 2024
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.
stuartmorgan-g commentedon Mar 21, 2024
@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.
LouiseHsu commentedon Mar 21, 2024
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.
hellohuanlin commentedon Mar 21, 2024
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).
If this is turns out hard to do, I think it's fine to just use class for everything.
[pigeon] Adds @SwiftClass annotation (#6372)
github-actions commentedon Apr 9, 2024
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 -vand a minimal reproduction of the issue.[pigeon] Adds @SwiftClass annotation (flutter#6372)
[pigeon] Adds @SwiftClass annotation (flutter#6372)