dart-lang / dart-lang/language
Allow a library prefix (not an import prefix) to support access to shadowed names
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
This proposal is a slight generalization of a proposal from @lrhn that occurred [here](https://github.com/dart-lang/sdk/issues/55329#issuecomment-2027854342).
The idea is that a library directive should be able to introduce a name for the library itself. This name can be used in a way that is similar to an import prefix to denote top-level or static member declarations that are shadowed by other declarations in intermediate scopes. It differs from an import prefix in that it can be used to denote a declaration whose name is private, and also in that it uniquely denotes the top-level namespace of the current library (import prefixes can denote other libraries, and they can be shared by more than one imported library).
A library 'lib.dart' would name itself (as an example, we're using the name `this_library`) as follows:
```dart
// --- Library 'lib.dart'.
library as this_library;
final int _foo = 10; // Example private declaration.
class E {} // Example public declaration.
```
We could then use it as follows, in 'lib.dart' or in a part which is a part of 'lib.dart':
```dart
class A { // `E` shadows a top-level declaration.
void _foo() {} // Shadows a top-level declaration.
void f() {
print(this_library._foo); // OK.
new this_library.E(); // OK.
}
}
```
Note that the underlying problem (shadowing) cannot always trivially be removed by renaming some declarations in the current library: A public static member can be used from other libraries, so we may not be in a position to rename that static member. However, it could still shadow some other declaration which is imported, which means that we also cannot easily rename that other declaration.
Similarly, we may not want to rename a class or a type variable with a shared public name even when they are declared in the same library: The class may be used by many clients outside this library, and the type variable name may be widely known because it occurs in documentation.
With generated code or macros, we may need to denote a declaration (possibly private) in a way that unambiguously resolves to a certain top-level or static member declaration, even in the case where the declarations in intermediate scopes are not known. This can safely be done in a named library, using constructs like `this_library._foo`.
### Grammar
```ebnf
::= 'library' ? ('as' )? ';'
```
### Static analysis
Assume that a library _L_ has been equipped with the name `N` using `library as N;` or `library D as N;` where `D` is derived from ``, and `N` is derived from ``.
A compile-time error occurs if a declaration or import prefix in _L_ has the name `N`. Otherwise, this introduces the name `N` into the library scope of _L_.
`N` does not belong to the exported namespace of _L_.
*This means that `N` shadows imported declarations named `N`, if any. Also, it behaves just like import prefixes with respect to exports.*
Assume that `N` is an identifier expression that occurs in _L_ and resolves to the prefix which is introduced by `` *(that is, it isn't shadowed)*. It is a compile-time error if it is not immediately followed by `.`.
*Just like an import prefix, `N` cannot be used as an expression, and it does not have a static type. Also note that `N?.id` is a compile-time error for any identifier `id`, and so is any cascade starting with `N..` or `N?..`.*
Assume that a qualified identifier of the form `N.id` occurs in _L_, where `id` is a private or a public name. This expression resolves to a declaration named `id` in the top-level scope of _L_, if it exists. A compile-time error occurs if it does not exist.
*This determines all further static analysis and the dynamic semantics of expressions starting with `N`.*
### Discussion
This is a very simple mechanism with a clear purpose. The ability to denote a top-level, imported, or static member declaration in a situation where some other declaration shadows it is generally useful, even though it is probably not used very often (we don't have it today because it wasn't needed that badly). It should be noted, though, that the ability to rule out shadowing in crucial for mechanically created code (such as plain code generation or macro execution), because there is no other way to ascertain that no shadowing can occur.
As an alternative, we could modify the rules about imports and exports such that an import of the library _L_ that occurs in _L_ itself could include private names, and such self-imports could then be used to overcome the shadowing problem.
However, that's a more complex undertaking because it changes the meaning of imports (now we need to learn that, as an exception, self imports _do_ include private declarations), and the resulting code is less explicit on the purpose and meaning of the self import. Even a [style rule](https://dart.dev/effective-dart/style#do-sort-sections-alphabetically) contributes to the confusion: The self import may occur somewhere in the middle of a number of other imports, and it may be confusing that this particular import "can see private names". Next, how about self imports via exports of other libraries? Do they not include private names? ... _my_ private declarations definitely can't be included when the exporting library is imported into some other library.
I think this adds up to a strong hint that we should use a separate mechanism rather than generalizing imports.
Contributor guide
Research direction
Start with the Grammar, Static analysis, and Discussion sections of the proposal to understand the intended library prefix semantics. No files or tests are named, so identify the relevant language specification and implementation entry points before proceeding. Done means the proposal has an agreed design and the affected language behavior is specified and validated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100