Skip to content

Optionally imported libraries. #4317

@lrhn

Description

@lrhn

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".

Metadata

Metadata

Assignees

No one assigned

    Labels

    requestRequests to resolve a particular developer problem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions