Skip to content

Optionally imported libraries. #4317

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
lrhn opened this issue Apr 3, 2025 · 7 comments
Open

Optionally imported libraries. #4317

lrhn opened this issue Apr 3, 2025 · 7 comments
Labels
request Requests to resolve a particular developer problem

Comments

@lrhn
Copy link
Member

lrhn commented Apr 3, 2025

See dart-lang/sdk#60466 for context.

Sometimes you want platform specific code in the same library as general code.
You can move all platform specific code into a conditionally imported library, with a mock library with the same interface as a default, so that the code will compile on other platforms, even if the mock library code will never be run (because it's guarded by a check to ensure it'll work).

Rather than this cumbersome workaround, what if one could conditionally import a library, without a default to fall back on, so that whether that library is imported or not can be checked in the code, and so that if not, code guarded by that check won't be considered an error if it refers to names that don't exist.

Proposal:

  • Allow import if (dart.library.whatnot) "library.uri" as whatnot; - an import with no default URI, which must have a prefix name, and which must not share that prefix with any other imports (just like an deferred import).
  • There can be more ifs for other cases, but if none of them are true, the import will be empty.
  • The prefix gets a special isAvailable getter (like a deferred import's loadLibrary) which is a constant that is true if something was imported and false if not. Any exported declaration of the library with that name is hidden.
  • Any conditional check of prefix.isAvailable enables the prefix on the true branch. This is tracked the same ways as type promotion or definite-assignment, so only locally inside a function body.
  • If not enabled, prefix.name is a compile-time error. Also, extensions from the library are not available.
  • When enabled, if a library is actually imported during a compilation, name access is just normal import scope access. Extensions work
  • When enabled, if a library was not imported, all name accesses on the prefix are allowed. The result has an unspecified meaning - like a value of type Never, except that it can also be used as a type or class reference, and a constant, so you can write prefix.Foo<int>.new(42) and not get an error. Basically, you can't get an error from a member access on the prefix, or downstream from that. It's dead code anyway. Also any instance member access is allowed. If it would be invalid, it's assumed to be an undefined extension.

The "this code can't fail" thing is the biggest challenge. It basically introduces a result of an operation which doesn't have to be a value, but can be a type, so prefix.foo<prefix.Bar>.new() is not an error because prefix.foo is "this unspecified thing".

@lrhn lrhn added the request Requests to resolve a particular developer problem label Apr 3, 2025
@FMorschel
Copy link

FMorschel commented Apr 3, 2025

I really doubt something like this would happen, but it would be probably good to add that any top level declaration called isAvailable is not accessible here too.

Something else, I'd say that extensions from that library could be used if they have an explicit extension override, WDYT?

@mateusfccp
Copy link
Contributor

I like this. I always felt like the way conditional imports in Dart works is kinda off. This would be a much more reasonable approach IMO.

@mateusfccp
Copy link
Contributor

I was wondering if we could have something different so we don't "pollute" the namespace with a virtual getter isAvailable, which would prevent a library to provide a real isAvailable getter. I couldn't think of anything, and none of the current reserved words for Dart seem to be a potential good fit for this.

@FMorschel
Copy link

FMorschel commented Apr 4, 2025

I was wondering if we could have something different so we don't "pollute" the namespace with a virtual getter isAvailable, which would prevent a library to provide a real isAvailable getter. I couldn't think of anything, and none of the current reserved words for Dart seem to be a potential good fit for this.

The only thing I could think of that would not use any new keyword would be for us to add an exception to conditions by making the library prefix to be possibly tested like:

import if (dart.library.whatnot) "library.uri" as whatnot;

void f() {
  if (whatnot) {
    whatnot.something;
  }
}

Similar to TS (I think) with nullable things but I'm not sure it would be the best approach here.

@mateusfccp
Copy link
Contributor

Yes, this would be a special case, because whatnot wouldn't be a boolean expression. I don't like special casing, but it may be better than isAvailable? I don't know.

@ykmnkmi
Copy link

ykmnkmi commented Apr 4, 2025

Can this apply to optional dependencies?

@zhxst
Copy link

zhxst commented Apr 17, 2025

Optionally import or Conditionally import, that's great. This maybe not as good as optionally dependencies, but it's definitely solve my flavor problem.
Use one plugin in flavor A, use another in plugin B. Currently this is not possible.
With Optionally import I can finally migrate my two branches into flavors, which will save a lot of work on code sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

5 participants