Open stefnotch opened 1 week ago
Great, this proposal is roughly what I want too! Some comments:
If none of the above were true, we go to the next file.
what if the file does not exist? do we throw or do we try the next path component?
In my understanding, a project could get away with not writing a single export
declaration if the file tree is well structured: all subfolders are adjacent to a file with the same name. This is close to what python does: a module is a folder that contains a file __init__py
(but python is more cryptic!). I'm slightly concerned that it would encourage ppl to create empty files just for that purpose. like in your example, I would be tempted to create an empty src/utils.wesl
so all files in src
can import util::anything
.
export
works like the mod
statement in rust, except that it can skip subfolders and can rename. Is this correct?some_name
into scope?export
statement?import ./src/utils/anything/blah
would resolve to /src/utils/anything/blah.wesl
, but never to declaration blah
in file /src/utils/anything.wesl
export "./src/utils" as utils; alias math = utils::math;
alias some_same = "./src/utils/shader"
. Essentiallly the quoted path is a regular ident. Then perhaps we could do things like accessing file declarations directly: let x = "./src/utils/shader"::sqrt(2.0);
. Actually you can do that in naga_oil
!Great, this proposal is roughly what I want too! Some comments:
If none of the above were true, we go to the next file.
what if the file does not exist? do we throw or do we try the next path component?
That is a really good question. Initially I was assuming that it's an immediate error, but after thinking about it for a bit, I don't actually see an issue with "just continue and try the next path component".
Maybe that should be proposal C?
Proposal A
* it seems to me that `export` works like the `mod` statement in rust, except that it can skip subfolders and can rename. Is this correct?
Yes, pretty much
* does an export also act as an import for the current module, i.e. brings `some_name` into scope?
No, it doesn't bring some_name
into scope. If the user wants some_name
, they can now import it with the usual mechanisms.
Proposal B
* does it mean that a file can have only one wildcard `export` statement?
Yes, exactly
* could you mix and match proposal A exports and a proposal B export, the latter acting as a catch-all?
I'm thinking that we should only pick one of them. Currently I prefer proposal A over proposal B.
* does it mean that the import can only resolve to a file module, and not a declaration inside the file? e.g. `import ./src/utils/anything/blah` would resolve to `/src/utils/anything/blah.wesl`, but never to declaration `blah` in file `/src/utils/anything.wesl`
Yes. That's a major limitation of proposal B. I actually haven't thought about this limitation, I only thought about the limitation below vvv.
* similarly, does it mean that if a submodule declares an inline module, it could never be imported from the outside?.
Also yes. Also a limitation of proposal B.
Additional notes
* re-exports could simply be aliases, since we can import aliases. `export "./src/utils" as utils; alias math = utils::math;`
Oh yes, that would also work! We could also do export file("./src/utils") as utils;
, juuuust in case the WGSL peeps ever want to add strings to WGSL so that we can have print("quoted string")
.
* similarly for proposal A, this could be an alternative equivalent syntax: `alias some_same = "./src/utils/shader"`. Essentiallly the quoted path is a regular ident. Then perhaps we could do things like accessing file declarations directly: `let x = "./src/utils/shader"::sqrt(2.0);`. Actually you can do that in `naga_oil`!
Similarly to above, we could let the syntax be let x = file("./src/utils/shader")::sqrt(2.0);
We can, of course, bikeshed over whether the function should be called file
or mod
or import
or ...
I think there are concerns here we might discuss separately, maybe as separate github issues. But I may be misunderstanding too. Anyway, here's my first take:
We want some way for packages to publish wgsl elements for use by other external importers from other packages and to customize the visible names and paths that. Allowing packages to choose their API seems like the most important use case here. Is that right?
@publish(some/name)
. Best to consider in the context of the use case we're trying to solve.The import system ought to be extensible to handle some future variant of namespaces/submodules. That's #25 I think.
Do we also want to enable some kind of remapping of import paths for internal to the package modules? e.g. some way to declare that henceforth all the elements in ./foo/bar/deep/deeper/zap.wgsl
can be imported from any module internal to the package with some shorter path? If we want this feature, I see how export "./src/utils/shaders/starry_shader" as starry
would get us there.
import starry
internally for convenience, but publish so that other packages import pkg/star_shader
;#define_module_path some_name
in the shader.wgsl
file. I can see how per file repathing/renaming would be a nice convenience for projects that like keeping the shaders in strings for example.import starry/foo
could refer to a local module in my package if I have the export
line above. If I later npm add
a package named starry, my code breaks mysteriously. Better to keep separate package names in a separate namespace.Also implied here as @k2d222 mentioned on discord and @ncthbrt brought up in #35, we might want replace the currently spec'd wildcard syntax. Worth considering, and also orthogonal probably.
@k2d222 @mighdoll @ncthbrt I updated the proposal, now it really is just
LGTM
Background
For future features, such as re-exports, and inline modules, we want an import system that has a concept of a module! One where
import foo::bar::baz
does not necessarily translate tofoo/bar/baz.wesl
. There could have been a re-export after all.The basic idea of an import is
import a::b
can mean, depending on the rest of the codea/b.wesl
.b
froma.wesl
a/utils/b.wesl
import a::b
can bring a module namedb
into scope. Or a function namedb
Caching import resolutions is explicitly an implementation detail. The algorithms described below will work without caching, but caching can speed them up considerably.
Resolving the source of an import is not always obvious. So here is a proposed algorithm.
Sparse Web Projects
Some web projects scatter
.wesl
files throughout their hierarchy. Usually the.wesl
files are reasonably concentrated, but there occasionally are folders do not have a wesl file. e.g.Basic Observation
Even without re-exports, or anything advanced, we need a resolution algorithm for
import a::b
! It can either import the moduleb
, from the filea/b.wesl
. Or it can import the functionb
, from the filea.wesl
.To solve that, we go over the import, step by step. At each step, we decide where to go next to find the correct module.
Step by step resolution
At the end of the resolution, we have a tree of modules. Each module has exactly one fully qualified path. (Future: Module re-exports will get resolved to the fully qualified path.)
First comes the root step.
.
means "absolute path of the current module"self
means the same thing.self
is a reserved word in WGSL, so it'd work.)..
is a shorthand for./..
super
)some_name
will look at thewesl.toml
#57Now we are at a valid module that we can parse.
Then comes the next step.
..
means "go one up in the tree of modules". If we escape the root folder of a dependency, we throw an error.super
means the same thing.super
is also a reserved word in WGSL.)some_name
. Now we look at the current module.current/module/path/some_name.wesl
Finally, we repeat that step until we are finished with our import. At the end, we either found a valid import, or ran into an error.