-
Notifications
You must be signed in to change notification settings - Fork 205
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
Import shorthand syntax #649
Comments
We can always make the choice to enforce extra rules for whitespace, but I don't even think that's particularly important. The following approach is grammar based (so whitespace is allowed everywhere), and it parses the examples without issues, as well as all the usual test files (so the grammar isn't broken): https://dart-review.googlesource.com/123407. |
This is one of the things that drove me crazy from the start. Would love to see this change. I'd rather see |
Overall, yes, I like this and think it can work. This is mostly a matter of taste, but I find it hard to like the leading It's a shame to give the elegant To get a better picture, I scraped a corpus and tried to gather (or in the case of internal code, fake) a representative set of imports. Here is the current syntax: import 'dart:isolate';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
import 'package:flutter/material.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:widget.tla.server/server.dart';
import 'package:widget.tla.proto/client/component.dart';
import 'test_utils.dart';
import '../util/dart_type_utilities.dart';
import '../../../room/member/membership.dart';
import 'src/assets/source_stylesheet.dart'; I think that covers all the different forms. With this proposal, those become: import dart:isolate;
import flutter_test;
import path;
import flutter:material;
import analyzer:dart/ast/visitor;
import widget.tla.server;
import widget.tla.proto:client/component;
import ./test_utils;
import ../util/dart_type_utilities;
import ../../../room/member/membership;
import ./src/assets/source_stylesheet; To me, the unequivocal wins are Here is an alternate idea: import dart:isolate;
import flutter_test;
import path;
import flutter:material;
import analyzer:dart.ast.visitor;
import widget.tla.server;
import widget.tla.proto:client.component;
import 'test_utils';
import '../util/dart_type_utilities';
import '../../../room/member/membership';
import 'src/assets/source_stylesheet'; The rules here are:
Using the Using quoted file imports does mean implicitly adding
So I think we're fine there. Again, I think the style proposed here is workable too. Either of these options would be a net improvement, I believe. |
Both are an improvement, but I'd prefer the latter. The former is weird with unquoted paths, particularly files from the same directory with the leading |
I must admit that using Using dots does make it look like a namespace. It's just that it isn't, It's not an internal Dart scope thing, but rather navigating in an external hierarchical path structure where the parts are not necessarily Dart identifiers, not even today, and making it look like it's Dart names worries me. As for: import 'src/assets/source_stylesheet'; without the trailing I think quoted strings should keep their current meaning, an unquoted imports (or differently quoted strings) are shorthands for an actual URI reference. |
We might want to special-case |
We'd have to preserve the ability to write imports with quotes, because file names can contain special characters (even a plain space would make parsing hard, e.g., Considering the "nice" paths that don't contain spaces and other obstacles, I don't see a problem with the slashes, and I actually tend to think it's good for readability that the syntax is similar to the URIs and paths that we see in many other contexts. Of course, focusing only on these "nice" cases and with no existing code to break, it's obviously an attractive idea to leave out This leaves the initial How about reusing the colon, e.g., We would use You might want to think that it's justified by "the current package can be denoted by the empty string", but that is actually not true (e.g., for a relative import of a library in |
I'd actually expect import :src/helper.dart would be another way to refer to local files, absolutely rather than relatively, but still relative on the package name. import "/src/helper.dart"; We could consider adding that as a feature too. |
Me too.
I considered a proposal where that was the only new sugar for "relative" imports. Basically tell users to make everything a package import, even for libraries in their own code. That obviously doesn't work for libraries outside
That... that works? :-O |
Ack, no. I thought I had changed (Update: It works!) |
@lrhn @munificent @eernstg What do you think of index.dart for folder widgets? like index.js
|
The idea, from a very cursory glance, seems to be that a directory can contain a "default" file that is imported if you import the directory. In Dart, it would mean that import "package:foo/bar/"; would automatically import "package:foo/bar/index.dart". We have traditionally used the package name as the default file in dart, so "package:foo/foo.dart" is the default name for "package:foo". Not sure whether that's better or worse than what is proposed here. You'll have to write less for sub-directories, but it requires the compiler to be able to recognize directories, something it can't if it fetches source from an |
I think this is also a good option |
I'm personally not a fan of adding syntax that relies on a convention for organizing libraries that doesn't already exist. I think this convention is already pretty well-established:
I'd rather have a syntax assume that convention and then users don't have to reorganize their code to get the greatest benefit from the new syntax. |
@munificent that's how I now use, but it's not so convenient. My personal subjective opinion. |
Is this mean that the following example will be valid?
An absolute import option from the root of the project/package would greatly increase the reusability of the code. |
I think that should work. The current proposal doesn't include a package-root-relative path, but I see no reason it can't. |
also |
One thing useful in typescript is the ability to create aliases for imports. so you can import like this
|
Are there objections to the proposal wherein imports are either a string (works like today), or a slash separated series of identifiers, where:
...? (Identifiers in this proposal are really dotted-identifiers, as in As far as I can tell this proposal handles all the common patterns and solves the "imports are a pain to write and ugly to read" thing that really is what for me motivates my interest in this issue. |
That's effectively "remove leading That works. It doesn't provide anything for relative paths, but those can use quotes. I'm OK with that. Probably for the best. It doesn't address package names with It can work. I'd prefer to use Should probably allow the path segments to not just be identifiers, but any sequences of ASCII letters, digits, underscore, dash and dot, as long as they're not all dots. |
I would like to suggest a non-breaking change which can happen without the complete proposal in place. Allowing import blocks import {
'dart:async',
'package:path/path.dart',
'package:flutter/material.dart'
} Instead of writing import multiple times import 'dart:async';
import 'package:path/path.dart';
import 'package:flutter/material.dart'; Mainly to improve readability, but requires less keystrokes the more imports we have. |
Not completely impossible. Unless the test of the import clause goes after the Still very little real benefit for adding new syntax. |
@lrhn import 'dart:async';
import 'package:path/path.dart';
import 'package:flutter/material.dart'; |
Thanks, I forgot. Can be an opportunity to remove the semicolon/terminator from imports as part of a larger effort to address issue #69. |
Rather than complicating the Dart language, why not implement this as an optional IDE code style or IDE plugin? Edit: I posted a question, so if you are going to down-vote me then how about explaining? |
Downvotes are a simple and quick way to say: "nah". With the context of your message people probably wanted to say "nah, there's no complication; rather, your optional code style proposal is actually way more complicated, while also not compliant with dart philosophy about formatting (it can't be opinionated)" |
This ER is trying to fix something that is not broken. Or at least not broken enough. The idea of dropping quotes, however, goes a bit too far IMO: the beauty of the unquoted paths is questionable - but on top of that, not every import can be migrated to the new syntax. People use Unicode characters in their paths (I've seen such examples, especially in tests), so they will have no option but to preserve the legacy syntax. (Do unquoted paths really look so much better? How many people will feel unhappy if the quotes are preserved?) The current import system in dart is very simple - much simpler than in most languages. E.g. rust import syntax looks nicer, but it imposes cumbersome restrictions on code organization. This is the price to be paid for a nicer syntax. :-) EDIT: if the goal is to improve readability, |
Maybe we should just do it and support Unicode anyway, just like we should for identifiers (#1283, which was, unfortunately, closed). |
Of all the challenges I face when writing Dart/Flutter code, "unnecessary repetitions" in my import statements is near the bottom of the list. I almost never read through the imports, and 99.9% of the time I don't even write the code because the IDE adds it automatically. |
I agree that we have more important stuff to discuss, why bother about "unnecessary repetitions" that the user never writes by himself? Pls, let's focus on getting meta-programming out and in a ready-to-use state so we all stop the "unnecessary repetition" of running code-gen tools ( |
For those following along, I've written an alternate proposal here based largely on this comment. |
I'm curious why import 'dart:isolate'; becomes import dart/isolate; rather than import dart:isolate; The import 'package:dart/isolate.dart'; Just as import 'package:flutter/material.dart'; |
@munificent For post-tokenization parsing, I'm starting to think that the best approach is to include all tokens up to the next whitespace, comment or semicolon, then reject if any of those tokens contain a non-accepted character. I think that means only accepting IDENTIFIER, RESERVED_WORD (I want to include those), NUMBER, HEX_NUMBER, The reason for including all the tokens is to have better error recovery. The shorthand starts at the next token after the |
Dots in the names are the root cause of all import syntax problems.
I wonder how dotted packages are used with other languages at google. Maybe there's a bit of extra configuration involved? I understand, internal users are humans, too, and their sentiment certainly counts. But maybe they can agree to pay a small tax in the form of a bit of extra configuration? That will allow the rest of the users to enjoy some more familiar format with dots or |
All? I think they're only problems if you want to use That is, every part of the thing we want to join up can already contain dots, so using dots as the separator between them never seemed viable, no more than using And then, there is no problem with (Even if there were no existing dotted package names, I'd still want to keep the option open, in case Pub's linear namespace gets too crowded, and we want to open up to Java-like domain based package names like |
Not only. If you try to use (remember, my question was: how dotted names are used at google with other languages? E.g. rust? Rust doesn't allow dots in module names. What gives?) Let's focus on the aesthetics of the import statement. The current format is nothing to brag about, for sure, but at least the ugly part is quoted. But without quotes, the syntax becomes strange and foreign: it doesn't rhyme with anything else in dart, cannot be reused in any other context, and leads to puzzlers like this import foo/bar; // based on munificent's proposal
import 'foo/bar.dart'; // relative import from the current package These two lines look similar, but their meaning is quite different. Same with import foo;
import 'foo.dart' Turns out, the 'package:' prefix was not that redundant; when you eliminate it, the notation becomes more cryptic and even more unfamiliar. Using colons and slashes doesn't make it much better. (An argument can be made that EDIT: here's another attempt to verbalize my grievances. |
Sure you can. It's trickier to parse because It's not how I'd define it, I'd probably go with |
But then, I have issues with this statement
But if Example: suppose there are 2 files called Caching the catalog of available packages is not unheard of (I suppose many build systems do that, especially with jar files). Maybe this is the right way to go? Suppose you want to use a globally unique name WDYT? |
There are many tools today which expect to be able to look at an import and know which part of it is the package name and which part is the path, and do not today need the entire package config to do this (as an example, the lint that you depend on referenced packages). This is not only useful for tools, but also useful for humans. I am strongly opposed to creating an import syntax where you cannot easily and syntactically, without global knowledge, know which part is the package name and which part is the path. |
Maybe I'm playing devil's advocate, but in rust, it's not that easy to find out how the logical structure is mapped to physical structure by just looking at the code (see https://stackoverflow.com/questions/73840738/how-exactly-does-rust-look-up-modules) Wouldn't it be useful for humans to be able to look at the declaration |
Isn't the existence of that stack overflow question itself indicative of the problem here? In dart we have a very concrete answer to that question. You look up the package name in the package config (at Just because you can't do that easily in other languages, doesn't make it bad? |
Sure it is! But only if we consider the issue of import in isolation.
(Probably there's more - I'm just guessing). Back to dart's import syntax: I think I made too big a deal out of dotted names. A typical user won't see such names very often; dart can support them without recommending them. But slashes don't play well with FQNs. In java, you never encounter underscores in package names (the style guide recommends Using You may also consider allowing to refer to the current package as |
A fully qualified name that includes the library, will include the equivalent of a URI, because that's what Dart uses to identify a library. I'm not opposed to something like URI:: identifier being able to denote any identifier, but I would still require the library to be imported first (I don't want to have to parse every function body to give the library dependencies). The goal here is to give a shorter, less noisy, syntax for import URIs, not to solve "qualified references in code". |
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:
The repetition alone is grating, and Dart imports can typically be split into three groups:
import "dart:async";
.import "package:built_value/built_value.dart";
.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:
:
, and a relative shorthand path./
,./
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:
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
andpart
declarations. It does not work forpart of
declarations becausepart of foo.bar.baz;
is already valid syntax. We could allow only relative (./
or../
) shorthands forpart 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:[a-zA-Z_$0-9./]
.;
, whitespace, quote or comment. (Tokenization will be very confusing if the shorthand URI can contain//
or/*
, or a string quite.)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 ofdart:async
will import"dart:async"
, and an import of justdart
is not allowed because there is nodart:dart
library. This allows us to treatdart:
as a platform supplied package with librariescore.dart
,async.dart
, etc., which may actually be an improvement over the current special-casing that we do. It does mean thatdart
is not available as a package name for user packages.Examples:
import built_value;
meansimport "package:built_value/built_value.dart";
import built_value:serializer;
meansimport "package:built_value/serializer.dart";
.import dart:async;
meansimport "dart:async";
.import ./src/helper;
meansimport "src/helper.dart";
.import /src/helper;
meansimport "package:current_package/src/helper.dart";
.The leading
./
for relative files in the same directory, is needed because otherwise we cannot distinguish whetherimport foo;
means import thefoo
package or the localfoo
file.import hide hide hide;
is valid and meansimport "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.
The text was updated successfully, but these errors were encountered: