Skip to content

[interop] Add Support for Anonymous Declarations #434

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

nikeokoronkwo
Copy link
Collaborator

@nikeokoronkwo nikeokoronkwo commented Aug 2, 2025

Fixes #385
Fixes #422
Fixes #410
Fixes #433

This PR adds support for generating declarations for anonymous declarations, such as anonymous objects, unions, closures and constructor types.

This PR also adds support for nullable types (types unioned with undefined and/or null).

A hashing function is used for consistently hashing string objects, which is used for hashing identifiers for anonymous unions, objects and more, as well as comparing such names to allow reusing of such types.

  • Anonymous Unions are turned into a Dart (anonymous) extension type which has a rep type/implements the shared type between the union parts. It has getters to get the value as a certain probability of the union. For instance, a union string | number | boolean may look like the following:
    extension type _AnonymousUnion_204398._(JSAny _) implements JSAny {
      String get asString => (_ as JSString).toDart;
      double get asDouble => (_ as JSNumber).toDartDouble;
      bool get asBool => (_ as JSBoolean).toDart;
    }
  • Anonymous Tuples are turned into an instance of a JSTupleX declaration generated depending on the number of members it may have (i.e if a 2 member tuple exists, it will be an instance of JSTuple2<A, B>). The tuple extension type looks like the following:
    // normal
    extension type JSTupleX<A, B, ..., B1>._(JSArray<JSAny> _) implements JSArray<JSAny> {
      A get $1 => _[0] as A;
      B get $2 => _[1] as B;
      /* ... */
      B1 get $x => _[x] as B1;
      
      set $1(A newValue) => _[0] = newValue;
      set $2(B newValue) => _[1] = newValue;
      /* ... */
      set $x(B1 newValue) => _[x] = newValue;
    }
    
    // readonly
    extension type JSReadonlyTupleX<A, B, ..., B1>._(JSArray<JSAny> _) implements JSArray<JSAny> {
      A get $1 => _[0] as A;
      B get $2 => _[1] as B;
      /* ... */
      B1 get $x => _[x] as B1;
    }
  • Anonymous Closures are turned into extension types implementing call. While Anonymous function types ((str: string) => void) have their call implementations as external, anonymous constructor types (new (str: string) => T to construct T) are implemented as function calls to create the given object. In the future after Make JSExportedDartFunction generic and add a toJSGeneric function sdk#54557, we should be able to replace this with a generic JSFunction.
    extension type _AnonymousFunction_123456._(JSFunction _) implements JSFunction {
      external void call(String str);
    }
    
    // Assuming T is defined elsewhere and is **not** a generic
    extension type _AnonymousFunction_123456._(JSFunction _) implements JSFunction {
      T call(String str) => T(str);
    }
  • Anonymous Objects are implemented as extension type objects with their default constructor as an object literal constructor. They also inherit any associated generic types alongside.

@nikeokoronkwo nikeokoronkwo marked this pull request as ready for review August 7, 2025 00:48
@nikeokoronkwo nikeokoronkwo requested a review from srujzs August 7, 2025 05:39
@_i1.JS()
external _AnonymousUnion_1189263 get responseObject;
@_i1.JS()
external _AnonymousConstructor_1059824 get productConstr;
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I may have miscommunicated earlier what I meant, but I was thinking of making these constructor values like

Product productConstr(
    num id,
    String name,
    num price,
  ) =>
      Product(
        id: id.toDouble(),
        name: name,
        price: price.toDouble(),
      );

instead. I'm not really sure it's useful for users to have this constructor as a getter (and it avoids the extra anonymous extension type if we don't), but I'm also totally okay with going the current route if you feel like it's better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

  • The constructor call shouldn't be a getter, it should be an override on call, so it is intended to be called as productConstr().
  • I do not know the implications of having it as a variable vs as a function.
  • I feel the anonymous extensions are useful, as in this example, they can be extended/implemented and typealiased.

Do you have any thoughts concerning these?

});

test('Sub Type Interface Test', () {
final a = InterfaceDeclaration(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd consider a case where we check the LCA of three types where the pairwise LCAs are different than the LCA of all the types. This is to check we're not accidentally composing the LCAs (which we aren't).

e.g.

C -> B -> A
E -> D -> B
F -> H -> B
E -> G -> A
F -> G -> A

LCA{E, F} = G but LCA{C, E, F} (B) != LCA{C, LCA{E, F}} (A)

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

Successfully merging this pull request may close these issues.

Interop Gen: Support Tuples Interop Gen: Support Function Types Interop Gen: Support Constructor Types Interop Gen: Support Union Types
2 participants