Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSError** params have unexpected generic private type #1864

Open
stuartmorgan opened this issue Jan 3, 2025 · 2 comments
Open

NSError** params have unexpected generic private type #1864

stuartmorgan opened this issue Jan 3, 2025 · 2 comments

Comments

@stuartmorgan
Copy link

stuartmorgan commented Jan 3, 2025

Generating NSBundle gives me the following methods:

  bool preflightAndReturnError_(
      ffi.Pointer<ffi.Pointer<objc.ObjCObject>> error) {
    return _objc_msgSend_1dom33q(
        this.ref.pointer, _sel_preflightAndReturnError_, error);
  }

  /// loadAndReturnError:
  bool loadAndReturnError_(ffi.Pointer<ffi.Pointer<objc.ObjCObject>> error) {
    return _objc_msgSend_1dom33q(
        this.ref.pointer, _sel_loadAndReturnError_, error);
  }

For clients this translates to Pointer<Pointer<_ObjCObject>>. This has all of the problems described in #1861 (and #1862 and #1863); I would expect this to be a public generic NSObject type rather than a private one if it's generic.

However, it also seems to be unexpectedly generic. I thought that was because I didn't include NSError in my ffi config, so I added it to avoid this problem... but it didn't; the type was still the same. I would expect the inner type to be NSError.

@liamappelbe
Copy link
Contributor

liamappelbe commented Jan 5, 2025

ffigen translates ObjC's NSError* to a Dart wrapper object NSError, for convenience (under the hood it's really a generic Pointer<_ObjCObject>). But it's not clear how we should translate NSError**. You can't make a Pointer to a Dart object like Pointer<NSObject>, and Dart doesn't have inout params or reference types. So I'm not sure how to support this pattern conveniently.

A) One option would be to just take a NSError wrapper object directly, and mutate its internal pointer. So the signature would be bool preflightAndReturnError_(NSError error), and effectively this would be like changing the msgSend call to _objc_msgSend_1dom33q(this.ref.pointer, _sel_preflightAndReturnError_, &(error.ref.pointer)), but with a few more steps because Dart doesn't have a & operator. But other parts of the interop story rely on the wrapper objects being immutable (passing the wrappers between isolates).

B) Another option would be to implement option A, but with another layer of indirection to solve the mutability issue. Add another pointer type, eg ObjCPointer<T> (name TBD), that just has a single mutable T value field. So the signature would change to bool preflightAndReturnError_(ObjCPointer<NSError> error), and internally it would mutate error.value.

C) Maybe the best way to translate these methods would be to change the signature to (bool, NSError) preflightAndReturnError_()? The only issue there is we're moving further away from a 1:1 translation of the method, increasing the chances of the user being confused by the translation, especially if there's more than one T** in the params, or if the T** isn't the last param.

One complication is that there are other uses of T** than this inout pattern. Maybe it's an array of T*? I'm sure that 99% of cases are inout params, but we need to have a way of supporting the remaining 1%. Might need to make this a per-method opt-in in the config. Also, we should probably also apply this logic to stuff like int*. It ends up being a pretty big feature.

I'll handle the privacy half of this issue in milestone 17, and if we can figure out how to translate T** more ergonomically then I'll implement it in milestone 18. Atm I'm leaning towards option B.

@stuartmorgan
Copy link
Author

B seems like the most intuitive option to me. C feels out of place to me at the FFI layer; it seems like the kind of thing that I would expect from an abstraction layer above FFI generation—especially because it is enforcing an opinionated conversion in a way that almost nothing else at this layer does. (E.g., is a Go-style record return the "right" Dart expression of this? Maybe it should be throwing an exception instead, to better align with Swift.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants