From cc05433f2d63af2fb0e39d3e983fc232c278f686 Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:33:06 -0700 Subject: [PATCH] Add amended-alias-resolution.md --- docs/amended-alias-resolution.md | 165 +++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 docs/amended-alias-resolution.md diff --git a/docs/amended-alias-resolution.md b/docs/amended-alias-resolution.md new file mode 100644 index 0000000..c4b89ab --- /dev/null +++ b/docs/amended-alias-resolution.md @@ -0,0 +1,165 @@ +# Amended Alias Syntax and Resolution Semantics + +## Summary + +We need to finalize a syntax for aliases in require statements and determine intuitive resolution semantics as we prepare to build a package management system. + +## Motivation + +As we prepare to build an official Luau package management system, we need unambiguous alias functionality. + +In an [old RFC for require-by-string](https://github.com/luau-lang/luau/pull/969), we removed the `@` prefix from aliases altogether and opted for the following design: +the path `require("my/module")` is resolved by first checking if `my` is an alias defined in a `.luaurc` file. +If so, we perform a string substitution on `my`. +If not, we try to resolve `my/module` relative to the requiring file. + +This design changed in the next few months and became what we now have in [require-by-string-aliases.md](require-by-string-aliases.md): +when requiring an alias, it must be prepended with the `@` prefix. +Otherwise, we will assume the given string does not contain an alias and will resolve it relative to the requiring file. + +There have been disagreements about which approach is more appropriate, so this RFC will serve as a way to document the ongoing discussion and the final decision we make. + +## Design and Drawbacks + +Below, we have multiple different options for syntax and resolution semantics. +For each design, assume that the following `.luaurc` file is defined: +```json +{ + "aliases": { + "libs": "/My/Libraries/Directory" + } +} +``` +Additionally, assume that we have a file named `dependency.luau` located in these two locations: +1. `./libs/dependency.luau` +2. `/My/Libraries/Directory/dependency.luau` + +### (1) Make explicit prefixes necessary + +#### (1a) Make aliases explicit with `@` prefix + +We make usage of the `@` prefix necessary in aliased require statements. +This means that any unprefixed string is always treated as an unaliased path. +```luau +-- Requires ./libs/dependency.luau +require("libs/dependency") + +-- Requires ./libs/dependency.luau +require("./libs/dependency") + +-- Requires /My/Libraries/Directory/dependency.luau +require("@libs/dependency") +``` + +The main drawback of this approach is that projects with many _external_ dependencies will have many `@` symbols appearing within require statements. + +This is similar to [Lune's approach](https://lune-org.github.io/docs/getting-started/2-introduction/8-modules#file-require-statements) and is identical to the approach currently described in [require-by-string-aliases.md](require-by-string-aliases.md). + +#### (1b) Make relative paths explicit with either `./` or `../` prefix + +We make usage of either the `./` or `../` prefix necessary in relative require statements. +This means that any unprefixed string is always treated as an aliased path. +```luau +-- Requires /My/Libraries/Directory/dependency.luau +require("libs/dependency") + +-- Requires ./libs/dependency.luau +require("./libs/dependency") + +-- Requires /My/Libraries/Directory/dependency.luau +require("@libs/dependency") +``` + +The main drawback of this approach is that projects with many _internal_ dependencies will have many `./` and `../` symbols appearing within require statements. + +This is similar to [darklua's approach](https://darklua.com/docs/path-require-mode/). + +### (2) Strongly recommend explicit prefixes + +We don't make the `@`, `./`, or `../` prefixes necessary, but we strongly recommend them. + +This can be accomplished by allowing unprefixed paths but throwing an error if they resolve to multiple files. +There is a performance cost to this: currently, if we successfully match a given path to a file, we stop searching for other matches. +If this approach were implemented, we would need to search for at most two matching files in case we need to throw an error. +In the case of multiple nested directories with their own `.luaurc` files and `paths` arrays, this search could take linear time with respect to the total number of paths defined, in the worst case. + +Of course, developers could use the `@`, `./`, or `../` prefixes to explicitly specify a search type and eliminate this performance cost. +```luau +-- Error: ./libs/dependency.luau exists and .luaurc defines a distinct "libs" alias +-- Would require ./libs/dependency.luau if "libs" alias did not exist +-- Would require /My/Libraries/Directory/dependency.luau if ./libs/dependency.luau did not exist +require("libs/dependency") + +-- Requires ./libs/dependency.luau +require("./libs/dependency") + +-- Requires /My/Libraries/Directory/dependency.luau +require("@libs/dependency") +``` + +This is a compromise between approaches (1a) and (1b) that sacrifices performance for compatibility and readability. + +## Alternatives + +### All explicit prefixes necessary + +An alternative approach is to combine approaches (1a) and (1b) and always require prefixes for disambiguation. +In other words, this is similar to approach (2), but any unprefixed path will _always_ result in an error. +This avoids the performance cost of approach (2), but it is not backwards compatible with existing code. +```luau +-- Error: must have either "@", "./", or "../" prefix +require("libs/dependency") + +-- Requires ./libs/dependency.luau +require("./libs/dependency") + +-- Requires /My/Libraries/Directory/dependency.luau +require("@libs/dependency") +``` + +### Use implicit resolution ordering to avoid `@` prefix + +If we choose to remove the `@` prefix, we must define an implicit resolution ordering. +In essence, aliases must either represent overrides or fallbacks for relative path resolution. +A drawback of this approach is that implicit resolution ordering can be confusing, and people disagree on which ordering is best. + +#### Aliases as overrides + +If aliases were overrides, we would have the following behavior. +This is identical to the [old RFC](https://github.com/luau-lang/luau/pull/969)'s behavior. +```luau +-- Requires /My/Libraries/Directory/dependency.luau +require("libs/dependency") + +-- Requires ./libs/dependency.luau +require("./libs/dependency") + +-- Error: there is no "@libs" alias in .luaurc +require("@libs/dependency") +``` + +#### Aliases as fallbacks + +If aliases were fallbacks, we would have the following behavior. +```luau +-- Requires ./libs/dependency.luau +require("libs/dependency") + +-- Requires ./libs/dependency.luau +require("./libs/dependency") + +-- Error: there is no "@libs" alias in .luaurc +require("@libs/dependency") +``` + +If `./libs/dependency.luau` did not exist, however, we would have the following: +```luau +-- Requires /My/Libraries/Directory/dependency.luau +require("libs/dependency") + +-- Error: does not exist +require("./libs/dependency") + +-- Error: there is no "@libs" alias in .luaurc +require("@libs/dependency") +``` \ No newline at end of file