-
Notifications
You must be signed in to change notification settings - Fork 33
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
RFC: Amended Require Syntax and Resolution Semantics #56
Conversation
Relative path parenting must already be done explicitly and is not up for discussion.
I'm torn personally. Originally I imagined this using @ personally (which I'd've been fine with - it's load bearing syntax!) but now that you mention it, it's normally nicer to explicitly mark relative requires. But of course, that's not how existing string requires work. Hmm. I'd say prioritise backwards compatibility with Lua (we want to give everyone the option to integrate with the wider Lua ecosystem at some point, right?). I don't think @ is that bad in the slightest, even though it's lamentable that people use relative paths as they do. |
It must be mentioned that I am of the opinion that having a require resolution order is a workaround for poor design, so I'm very firmly in the camp of 1c. A softer version I'm okay with, given sufficient persuasion, is 1a. All other designs are no-go. |
Lua uses a different convention entirely with Regarding backwards compatibility with existing Luau code, it seems there are two conventions used in different codebases. In the past Luau was more restrictive, requiring |
My preference is for 1b, relative requires must start with
Unfortunately, this also breaks some existing Luau code but I'm hoping since we're discussing this early enough that the impact is minimal. |
Got it. I was under the impression Lua used normal fs paths, thanks for the correction :) I'll change my answer then to reflect that relaxed constraint:
Firm agree. In general, it should be clear and unambiguous what you're trying to require, at the point of the require. I don't want to have to go hunting dependencies down.
Yeah, I personally think At that point, I suppose you can go with or without |
I want to wait for @vegorov-rbx and @aatxe to express their preferences on the designs, but it does seem like we're leaning in the direction of the explicit resolution methods (1a, 1b, 1c), rather than the implicit ones (3a, 3b). The "almost-explicit" (2) is really just a less performant version of (1c) that tries to minimize breaking backwards compatibility—if we don't care about this, then I think (1c) is strictly better than (2). One confusing case with (2) that I can imagine is a developer's I'm a fan of (1c). Despite it being a bit uglier, I think it's the responsibility of the language to be unambiguous, and the responsibility of an LSP to make it easy for the developer (i.e. autocomplete). With (1c), autocomplete can have a clear indication of when to branch to a new set of possibilities. -- Autocomplete options: "@", "./", "../"
require(" -- Autocomplete options: all available aliases
require("@ -- Autocomplete options: all available modules in requirer's directory
require("./ In contrast, (1a) and (1b) are slightly more confusing in terms of autocomplete options. -- Autocomplete options: need to show valid string path components and all three prefixes ("@", "./", "../")
-- "Valid string path components" means local modules for (1a) and aliases for (1b)
require(" |
1A, 1B, and 1C all seem fine to me. I think there is value in 1C since it is explicit and enables better autocomplete support. |
All of the (1) options are good with me, with a preference towards 1B or 1C. I'm okay with the idea that libraries are the default (and therefore aliases are the default), hence 1B, and 1C is more-or-less the same to me, just with library names being under |
Judging by the vibes in these replies, I guess the question is just whether we think relative paths are more frequently used than aliases. We could probably try and get some metrics for this if we wanted data. Does anyone have strong feelings about relative paths without a prefix? |
We also care about how we want to design our require syntax as we think about a package manager. If backwards compatibility isn't a high priority, then it might not matter if relative requires are used more frequently right now. For example, if our ultimate goal is to support modular workflows that involve aliased imports (facilitated by a package manager), then it may make more sense to lean away from (1a), which simplifies the syntax for relative paths instead. My point is that our goals for future usage patterns may matter more than current usage patterns, depending on how highly we prioritize backwards compatibility. |
Fair point, I hadn't considered that. I guess something else we should remember is that we can always "reserve" the unprefixed variant and make this choice when we have more of an idea of how package management is going to affect all of this, and use both Though personally I'm already leaning towards mandating |
I think option 3 is unacceptable, but that thankfully seems to be a very popular opinion. In the same vein, 2 is just a worse version of 1c. If you make something optional, it's probably not going to be used by the bulk of users, so we should just bite the bullet and make it required. I don't super care for requiring a prefix for relative requires, because I like being able to go |
Took a look at our internal Luau codebase. 20090 total require statements.
|
I've also moved approaches (2), (3a), and (3b) to alternatives. It seems like we're in favor of the explicit options and now just need to choose from (1a), (1b), and (1c). @dphblox and @Dekkonot both mentioned a great point about how (1c) effectively reserves unprefixed paths, so I just added a small section to (1c) about it being forwards-compatible with both (1a) and (1b). |
Agreed on path resolution being weird. I really don't think we should be using path resolution at all. It strikes me as exactly the kind of implicit behaviour that people here are taking issue with - better to explicitly alias? As for |
Autocomplete doesnt exist, I mean it'll warn you for providing an invalid path but that's it. |
When we started discussing this internally, the options we were considering didn't seem like they'd impact the I agree that
With (1b) and (1c), it's unclear what syntax could support the paths array fallback search. For (1c), we could allow unprefixed paths but only resolve them relative to As far as compatibility with
I'm not sure we want to completely get rid of |
I suppose to be clearer about why I don't like it, it's because I don't like it when projects share packages (e.g. having one folder in the root of C:/ or wherever, where all installed packages like Roact etc live). It leads to all sorts of problems, especially when two projects need a package of the same name, but different versions (or worse - two unrelated packages with a name collision). I feel like I suppose all of this'll end up being the responsibility for whatever package management system we land on though, so maybe it's OK to have? I'm not sure. It feels a little confused to me, I guess? Maybe I don't understand what |
Agreed with all the points raised by @dphblox. Personally I'm not sold on the use cases of Maybe it's not that implicit, but the ordering still matters. For The behaviour of paths can also be replicated using aliases instead (e.g I would argue for actually removing paths support completely, but maybe that's too strong / it's too late. |
Not completely related to this RFC, so feel free to ignore, but also worth considering how I cannot see a world where paths would be usable when you want to have portable packages, apart from if the only paths listed there are within the scope of the package itself (or its pointing to the Packages/ or node_modules/-like folder generated by the package manager). But to be fair, the same restriction would probably apply to aliases too. But I think those types of aliases are still useful, whilst "only-within-package-scope" paths don't seem like it to me. I haven't thought about that too deeply though (and I may be biased against paths at this point :P) so maybe you folks have already considered this. |
Discussed with @aatxe a bit just now. The initial motivation for Our package manager implementation could utilize
I'll refactor this RFC to propose removing the |
To help this proposal move forward now, I think the best course of action is to formally propose (1c) and move (1a) and (1b) to alternatives. Approach (1c) is the most restrictive of the three options and prevents us from making a decision about unprefixed paths before we know what the most common use case will be (say, in the context of a package manager). Despite being the most restrictive, it feels like a good compromise to me because of this forward compatibility with both (1a) and (1b). I get your point here @Dekkonot:
Still, I'm a bit wary about going in the direction of (1a), building a package manager, and then getting similar complaints about the mandated I think the best option is to choose the approach that gives us the most flexibility with unprefixed paths. Unless someone has strong opposing thoughts on this, I think we should move forward with (1c). |
Reworked the RFC: would appreciate PR review now. I'll plan on merging in these changes in a week if there is no opposition. |
One last thing I'm not currently seeing in the RFC. If we still support both Given this scenario:
If I have I would like to see us stop supporting |
Two sane behaviors IMO are: prefer luau to lua, or error about the conflict. |
My preference would be an error in that situation, because I cannot imagine it being deliberate. |
I've added a section about throwing an error in any case in which there's ambiguity, like @alexmccord's example:
I think that's in line with making everything else explicit that we've discussed in this PR. |
On the topic of ambiguity, would this also error if multiple files shared the same name? This isn't possible on disk but is possible in the DataModel. Right now when performing an ambiguous indexing operation in Roblox, it always has to succeed for backcompat reasons, but perhaps we could take this opportunity to explicitly disallow it for this new syntax, and for any path resolution style APIs going forward? |
@dphfox and I discussed this internally:
We're going to leave Roblox-related details out of the scope of this RFC, but the goal is to keep things as compatible as possible. In Luau's implementation, I don't think we need to add an additional check for this, as the file system implicitly enforces it for us. |
That's a fair attitude to take. It's an implementation detail that Roblox is a graph. Basically everywhere else Luau exists it shouldn't matter. |
# General Updates * Fix some cases where documentation symbols would not be available when mouseovering at certain positions in the code * Scaffolding to help embedders have more control over how `typeof(x)` refines types * Refinements to require-by-string semantics. See luau-lang/rfcs#56 for details. * Fix for #1405 # New Solver * Fix many crashes (thanks you for your bug reports!) * Type functions can now call each other * Type functions all evaluate in a single VM. This should improve typechecking performance and reduce memory use. * `export type function` is now forbidden and fails with a clear error message * Type functions that access locals in the surrounding environment are now properly a parse error * You can now use `:setindexer(types.never, types.never)` to delete an indexer from a table type. # Internal Contributors Co-authored-by: Aaron Weiss <[email protected]> Co-authored-by: Hunter Goldstein <[email protected]> Co-authored-by: Varun Saini <[email protected]> Co-authored-by: Vyacheslav Egorov <[email protected]>
Rendered
We need to finalize a syntax for aliases in require expressions and determine intuitive resolution semantics as we prepare to build a package management system.
Specifically, this proposal finalizes a syntax for require expressions, removes the
paths
configuration variable, and throws errors when requiring paths that cannot be disambiguated to a script.