From 0e1f1b454b792bc49c87efe7de575f1f6f80ff5d Mon Sep 17 00:00:00 2001 From: Ilya Klyuchnikov Date: Fri, 1 Sep 2023 11:46:34 +0100 Subject: [PATCH] EEP 65: -import_type directive --- eeps/eep-0065.md | 133 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 eeps/eep-0065.md diff --git a/eeps/eep-0065.md b/eeps/eep-0065.md new file mode 100644 index 0000000..b784217 --- /dev/null +++ b/eeps/eep-0065.md @@ -0,0 +1,133 @@ + Author: Ilya Klyuchnikov + Status: Draft + Type: Standards Track + Created: 01-Sep-2023 + Post-History: +**** +EEP 61: `import_type` Directive +---- + +Abstract +======== + +This EEP proposes a new `-import_type(Module, [Types])` module directive +to import remote types. Imported types can be referenced the same way as local +types, eliminating the need for a module prefix. + +Rationale and motivation +======== + +The reasoning behind the proposal aligns with the existing +`-import(Module, [Functions])` directive. The primary motivations for +introducing the directive are: + +- **Simplification in modules with heavy usage of remote types**. In scenarios + where a module frequently references a specific remote type, using its fully + qualified name can become tedious. For example, the `erl_lint` module defines + a [local type anno()](https://github.com/erlang/otp/blob/5bd165d16a62d6fa30118f846d52c016141b606e/lib/stdlib/src/erl_lint.erl#L88) + to refer to the type `erl_anno:anno()`. +- **Reducing verbosity for ubiquitous types**. In certain applications + or projects, some types might be so commonly used that referencing them with + their full names becomes overly verbose. A common workaround is to place these + types in a header file and then include this header file in most of the + application's modules. An example: common types in the + [dialyzer.hrl](https://github.com/erlang/otp/blob/5bd165d16a62d6fa30118f846d52c016141b606e/lib/dialyzer/src/dialyzer.hrl#L107-L176) + header file. A downside of this approach is that it creates an additional load + for tooling (as dialyzer and type-checkers). Each module ends up with its own + local copy of the type, which can result in repeated comparisons of different + versions of the same type and slowing down the analysis. + +It makes exporting and importing types symmetrical to exporting and importing +functions. Until now, there is an asymmetry: types can be exported but cannot be +imported. + +Details +======== + +A new module directive is introduced: + + -import_type(Module, [T1/A1, ..., Tk/Ak]). + +Here the `Module` is an atom (the name of the module from which types are +imported), the `Ti`'s are atoms (the name of the type) and `Ai`'s are integers +(the arities). Imported types can be referenced the same way as local types - +without any module prefix. + +Example: + + -module(m1). + -import_type(common_types, [user/0, id/0]). + + -spec get_user_id(user()) -> id(). + +A reference to a type should be unambiguously resolved to a locally defined +type, predefined built-in type or imported type. That imposes a few restrictions +(similar to restrictions for importing functions): + +1. An import directive cannot override a built-in type. +2. A type `Ti/Ai` can be imported only once. +3. An imported type cannot be redefined locally in the module. + +The restrictions are validated by the compiler (as part of the `erl_lint` +stage). It is up to tooling to check whether the imported remote type exists +and exported, the compiler doesn't check it. + +Examples of restrictions: + +Overriding a built-in type: + + -module(m2). + -import_type(m1, [binary/0]). + + error: import directive overrides auto-imported builtin type binary/0 + +Importing twice: + + -module(m). + -import_type(m1, [user/0]). + -import_type(m2, [user/0]). + + error: type user/0 already imported from m1. + +Importing twice from the same module: + + -module(m). + -import_type(m1, [a/0, b/0]). + -import_type(m1, [b/0, c/0]). + + error: type b/0 already imported from m1 + +Redefining imported type: + + -module(m). + -import_type(m1, [user/0]). + + -type user() :: {user, binary()}. + + error: defining imported type user/0 + +Reference implementation +======== + + + +The implementation consists of three logical pieces: + +- A change in the parser (`erl_parse`) to support the new directive. +- A change in `erl_lint` to enforce restrictions (from the _Details_ section) +- Expansion of imported types is handled by the same compiler pass that handles + function imports. So, imported functions and imported types are handled + the same way. Dialyzer already gets expanded imported types, so it + "just works". + +Backwards compatibility +======== + +The change is backward compatible. Existing code cannot have the proposed +`import_type` attributes, since they are not parseable by the current compiler. + +Copyright +========= + +This document is placed in the public domain or under the CC0-1.0-Universal +license, whichever is more permissive.