dart-lang / dart-lang/language

Import shorthand syntax

Open
#649 79 comments 290 reactions 1 assignee Claimed by @lrhn View on GitHub
brevity feature import-shorthand small-feature unquoted-uris
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

Most recent version:
https://github.com/dart-lang/language/blob/main/accepted/future-releases/unquoted-imports/feature-specification.md

Original design below (which also has some good parts, a final result might be something in-between).

-----------------------------

This is a proposal for a shorter import syntax for Dart. It is defined as a shorthand syntax which expands to, and coexists with, the existing import syntax. It avoids unnecessary repetitions and uses short syntax for the most common imports.

## Motivation

Dart package imports are fairly verbose because they are based on URIs with no shorthands. A fairly typical import would be:

```dart
import "package:built_value/built_value.dart";
```

The repetition alone is grating, and Dart imports can typically be split into three groups:

* Platform libraries, `import "dart:async";`.
* Third-party packages, `import "package:built_value/built_value.dart";`.
* Same package relative import, `import "src/helper.dart";`.

The package imports are the ones with most overhead. For the rest, the surrounding quotes and trailing `.dart` are still so ubiquitous that they might as well be assumed.

## Syntax

The new syntax uses no quotes. Each shorthand library reference is provided as a *URI-like* character sequence containing no whitespace, and consisting only of identifiers/reserved words separated or prefixed by colons (`:`), dots (`.`) and slashes (`/`).

The allowed formats are:

* A single shorthand Dart package name.
* A shorthand Dart package name followed by a colon, `:`, and a relative shorthand path.
* A `/`, `./` or `../` followed by a relative shorthand path.

A *shorthand Dart package name* is a *dotted name*: A non-empty `.` separated sequence of Dart identifiers or reserved words. Such a sequence can have just a single element and no separator.

A *relative shorthand path* is a non-empty `/` separated sequence of dotted names.

The grammar would be:

```
# Any sequence of letters, digits, `_` and `$`.
::=
| ? ( | )

::=
| '.'

::=
| '/'

::=
(':' )?
| './'
| '../'
| '/'

::= ...
|
```

Since a shorthand URI can only occur where a URI is expected, and a URI is currently always a string, there is no ambiguity in *parsing*. Tokenization is doable, but will probably initially allow whitespace between tokens because it doesn't yet know that it's a shorthand sequence. When it recognizes that a URI is expected and a non-string follows, it must combine the following tokens only as long as there is no space between them.

We can allow spaces between identifiers/keywords and `:`, `.` and `/`, but it will be harder to read and it makes the grammar less extensible.

The shorthand syntax can also be used for `export` and `part` declarations. It does not work for `part of` declarations because `part of foo.bar.baz;` is already valid syntax. We could allow only *relative* (`./` or `../`) shorthands for `part of` declarations, or we may want to disallow this existing syntax so that you can use the full shorthand syntax with no exceptions.
(Please do disallow the old part-of format where you use the parent library *name*).

(Not sure this works as written. Something like `x.2.4e2` can be parsed as containing a double literal. The grammar above doesn't allow that. A less distinguishing approach could be:
* Require a shorthand to start with `[a-zA-Z_$0-9./]`.
* Include every character up to the first following `;`, whitespace, quote or comment. (Tokenization will be very confusing if the shorthand URI can contain `//` or `/*`, or a string quite.)
* Check later whether its valid according to the grammar above.

That seems more viable than trying to guess which tokens can be accepted, just accept any that can be included whole into the combined lexeme.)

## Semantics

An import of a single-identifier package name, `name`, is equivalent to an import of `"package:name/name.dart"`. This is the most common form of package imports, and it gets the shortest syntax.

An import of a dot-separated package name, `some.prefix.last`, is equivalent to an import of `"package:some.prefix.last/last.dart"`. The single-identifier case is just the special case where there is no prefix.

An import of a package-colon-path sequence, `name:path` is equivalent to an import of `"package:name/path.dart"`. (Notice the added `.dart`). This is used for packages which expose more than one library.

An import of a relative URI path, *path*, one starting with `/`, `./` or `../`, is equivalent to an import of "*path*.dart".

The package name `dart` is special cased so that an import of `dart:async` will import `"dart:async"`, and an import of just `dart` is not allowed because there is no `dart:dart` library. This allows us to treat `dart:` as a platform supplied package with libraries `core.dart`, `async.dart`, etc., which may actually be an improvement over the current special-casing that we do. It does mean that `dart` is not available as a package name for user packages.

Examples:

* `import built_value;` means `import "package:built_value/built_value.dart";`
* `import built_value:serializer;` means `import "package:built_value/serializer.dart";`.
* `import dart:async;` means `import "dart:async";`.
* `import ./src/helper;` means `import "src/helper.dart";`.
* `import /src/helper;` means `import "package:current_package/src/helper.dart";`.

The leading `./` for relative files in the same directory, is needed because otherwise we cannot distinguish whether `import foo;` means import the `foo` package or the local `foo` file.

* `import hide hide hide;` is valid and means `import "package:hide/hide.dart" hide hide;`.
* `import pkg1 if (dart.libraries.io) pkg2;` works too, each URI is expanded individually.

## Consequences

Programmers can write less code. There will be some paths which cannot be written in the shorthand syntax, perhaps because they contain non-identifier characters or path segments starting with a digit. Those will still have to be written the old way, inside delimited strings.

The parser needs to be a little clever. If it tokenizes identifiers, reserved words, dots, colons and slashes first, then it has to combine them back into a single shorthand URI and check for separating whitespace. The reason this proposal does not allow even more complicated shorthand URIs is that it would make parsing even more problematic. The chosen design attempts a trade-off between allowing most existing package URIs to be written with the new syntax and allow the syntax to be parsed without too much overhead.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.