Skip to content

Commit 1eabfd3

Browse files
committed
Improve naga_oil parity coverage
1 parent 0b09d0b commit 1eabfd3

16 files changed

Lines changed: 344 additions & 9 deletions

README.mbt.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ test "README: compose registered modules" {
147147
defines,
148148
value_defines: @moon_wgsl.default_wgsl_value_defines(),
149149
redirects,
150+
additional_imports: [],
150151
}
151152
let composed : String = composer.compose_wgsl(
152153
"sprite_render/mesh2d/mesh2d.wgsl", compose_options,
@@ -194,6 +195,7 @@ test "README: bulk registry and relative imports" {
194195
defines,
195196
value_defines: @moon_wgsl.default_wgsl_value_defines(),
196197
redirects,
198+
additional_imports: [],
197199
}
198200
let composed : String = composer.compose_wgsl(
199201
"shaders/effects/main.wgsl", compose_options,
@@ -264,6 +266,7 @@ test "README: scan source tree" {
264266
defines,
265267
value_defines: @moon_wgsl.default_wgsl_value_defines(),
266268
redirects,
269+
additional_imports: [],
267270
}
268271
let composed : String = composer.compose_wgsl(
269272
"effects/main.wgsl", compose_options,
@@ -324,6 +327,7 @@ test "README: export single WGSL file" {
324327
defines,
325328
value_defines: @moon_wgsl.default_wgsl_value_defines(),
326329
redirects,
330+
additional_imports: [],
327331
}
328332
let export_options : @moon_wgsl.WgslExportOptions = { root_items: ["shade"] }
329333
let exported : @moon_wgsl.WgslExportOutput = composer.export_wgsl_with_options(
@@ -367,6 +371,7 @@ test "README: build source catalog" {
367371
defines,
368372
value_defines: @moon_wgsl.default_wgsl_value_defines(),
369373
redirects,
374+
additional_imports: [],
370375
}
371376
let catalog : Array[@moon_wgsl.WgslSourceCatalogEntry] = composer.build_wgsl_source_catalog(
372377
compose_options,
@@ -410,6 +415,7 @@ test "README: source-level redirects" {
410415
defines,
411416
value_defines: @moon_wgsl.default_wgsl_value_defines(),
412417
redirects,
418+
additional_imports: [],
413419
}
414420
let export_options : @moon_wgsl.WgslExportOptions = { root_items: ["shade"] }
415421
let exported : @moon_wgsl.WgslExportOutput = composer.export_wgsl_with_options(
@@ -514,6 +520,8 @@ Important public data structures:
514520
- `WgslDirectives`
515521
- `WgslSourceFile`
516522
- `WgslComposeOptions`
523+
Holds root compose settings: asset base, shader defs, value defs, symbol
524+
redirects, and root-only `additional_imports`.
517525
- `WgslSymbolRedirect`
518526
- `WgslExportOptions`
519527
- `WgslExportOutput`

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ test "README: compose registered modules" {
147147
defines,
148148
value_defines: @moon_wgsl.default_wgsl_value_defines(),
149149
redirects,
150+
additional_imports: [],
150151
}
151152
let composed : String = composer.compose_wgsl(
152153
"sprite_render/mesh2d/mesh2d.wgsl",
@@ -194,6 +195,7 @@ test "README: bulk registry and relative imports" {
194195
defines,
195196
value_defines: @moon_wgsl.default_wgsl_value_defines(),
196197
redirects,
198+
additional_imports: [],
197199
}
198200
let composed : String = composer.compose_wgsl(
199201
"shaders/effects/main.wgsl",
@@ -261,6 +263,7 @@ test "README: scan source tree" {
261263
defines,
262264
value_defines: @moon_wgsl.default_wgsl_value_defines(),
263265
redirects,
266+
additional_imports: [],
264267
}
265268
let composed : String = composer.compose_wgsl(
266269
"effects/main.wgsl",
@@ -320,6 +323,7 @@ test "README: export single WGSL file" {
320323
defines,
321324
value_defines: @moon_wgsl.default_wgsl_value_defines(),
322325
redirects,
326+
additional_imports: [],
323327
}
324328
let export_options : @moon_wgsl.WgslExportOptions = {
325329
root_items: ["shade"],
@@ -367,6 +371,7 @@ test "README: build source catalog" {
367371
defines,
368372
value_defines: @moon_wgsl.default_wgsl_value_defines(),
369373
redirects,
374+
additional_imports: [],
370375
}
371376
let catalog : Array[@moon_wgsl.WgslSourceCatalogEntry] =
372377
composer.build_wgsl_source_catalog(compose_options)
@@ -408,6 +413,7 @@ test "README: source-level redirects" {
408413
defines,
409414
value_defines: @moon_wgsl.default_wgsl_value_defines(),
410415
redirects,
416+
additional_imports: [],
411417
}
412418
let export_options : @moon_wgsl.WgslExportOptions = {
413419
root_items: ["shade"],
@@ -518,6 +524,8 @@ Important public data structures:
518524
- `WgslDirectives`
519525
- `WgslSourceFile`
520526
- `WgslComposeOptions`
527+
Holds root compose settings: asset base, shader defs, value defs, symbol
528+
redirects, and root-only `additional_imports`.
521529
- `WgslSymbolRedirect`
522530
- `WgslExportOptions`
523531
- `WgslExportOutput`

composer.mbt

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,48 @@ fn copy_import_definitions(
8585
copied
8686
}
8787

88+
///|
89+
fn serialize_wgsl_import_definition(
90+
import_definition : ImportDefinition,
91+
) -> String {
92+
let import_path = import_definition.import_path.trim().to_string()
93+
if import_path == "" {
94+
return ""
95+
}
96+
let items : Array[String] = []
97+
for item in import_definition.items {
98+
let trimmed_item = item.trim().to_string()
99+
if trimmed_item != "" {
100+
items.push(trimmed_item)
101+
}
102+
}
103+
if items.length() == 0 {
104+
"#import \{import_path}"
105+
} else {
106+
let item_list = items.join(", ")
107+
"#import \{import_path} \{item_list}"
108+
}
109+
}
110+
111+
///|
112+
fn prepend_wgsl_additional_imports(
113+
source : String,
114+
additional_imports : Array[ImportDefinition],
115+
) -> String {
116+
let lines : Array[String] = []
117+
for import_definition in additional_imports {
118+
let line = serialize_wgsl_import_definition(import_definition)
119+
if line != "" {
120+
lines.push(line)
121+
}
122+
}
123+
if lines.length() == 0 {
124+
return source
125+
}
126+
let import_block = lines.join("\n")
127+
"\{import_block}\n\{source}"
128+
}
129+
88130
///|
89131
fn copy_bool_defs(
90132
defs : @hashmap.HashMap[String, Bool],
@@ -126,6 +168,7 @@ priv struct WgslComposeSession {
126168
value_defines : @hashmap.HashMap[String, Int]
127169
modules : @hashmap.HashMap[String, String]
128170
redirects : Array[WgslSymbolRedirect]
171+
additional_imports : Array[ImportDefinition]
129172
visited : @hashmap.HashMap[String, Bool]
130173
imported_item_names : @hashmap.HashMap[String, String]
131174
active_rel_paths : Array[String]
@@ -255,6 +298,7 @@ fn Composer::new_wgsl_compose_session(
255298
value_defines: copy_int_defs(options.value_defines),
256299
modules: self.registry.copy_import_module_paths(),
257300
redirects: options.redirects.copy(),
301+
additional_imports: copy_import_definitions(options.additional_imports),
258302
visited: @hashmap.HashMap::new(),
259303
imported_item_names: @hashmap.HashMap::new(),
260304
active_rel_paths: [],
@@ -676,9 +720,16 @@ pub fn Composer::add_composable_module(
676720
effective_defs.remove(def_name)
677721
}
678722

723+
let source_with_additional_imports = prepend_wgsl_additional_imports(
724+
desc.source,
725+
additional_imports,
726+
)
679727
let definition = ComposableModuleDefinition::{
680728
name: module_name,
681-
sanitized_source: metadata.cleaned_source,
729+
sanitized_source: prepend_wgsl_additional_imports(
730+
metadata.cleaned_source,
731+
additional_imports,
732+
),
682733
language: desc.language,
683734
file_path: desc.file_path,
684735
shader_defs,
@@ -691,7 +742,7 @@ pub fn Composer::add_composable_module(
691742
ignore(
692743
self.registry.register_source(
693744
registry_rel_path,
694-
desc.source,
745+
source_with_additional_imports,
695746
Some(module_name),
696747
),
697748
)
@@ -1266,7 +1317,7 @@ fn Composer::preprocess_wgsl_source_with_path(
12661317
}
12671318

12681319
///|
1269-
fn Composer::load_wgsl_preprocessed_into_session(
1320+
fn Composer::load_root_wgsl_preprocessed_into_session(
12701321
self : Composer,
12711322
rel : String,
12721323
session : WgslComposeSession,
@@ -1286,7 +1337,9 @@ fn Composer::load_wgsl_preprocessed_into_session(
12861337
session.resolved_source_files.set(normalized_rel, source)
12871338
session.active_rel_paths.push(normalized_rel)
12881339
let composed = self.preprocess_wgsl_source_with_path(
1289-
normalized_rel, source, session,
1340+
normalized_rel,
1341+
prepend_wgsl_additional_imports(source, session.additional_imports),
1342+
session,
12901343
)
12911344
session.active_rel_paths.pop() |> ignore
12921345
composed
@@ -1299,7 +1352,7 @@ pub fn Composer::compose_wgsl(
12991352
options : WgslComposeOptions,
13001353
) -> String raise ComposerError {
13011354
let session = self.new_wgsl_compose_session(options)
1302-
self.load_wgsl_preprocessed_into_session(rel, session)
1355+
self.load_root_wgsl_preprocessed_into_session(rel, session)
13031356
}
13041357

13051358
///|
@@ -1309,7 +1362,11 @@ pub fn Composer::compose_wgsl_source(
13091362
options : WgslComposeOptions,
13101363
) -> String raise ComposerError {
13111364
let session = self.new_wgsl_compose_session(options)
1312-
self.preprocess_wgsl_source_with_path("", source, session)
1365+
self.preprocess_wgsl_source_with_path(
1366+
"",
1367+
prepend_wgsl_additional_imports(source, session.additional_imports),
1368+
session,
1369+
)
13131370
}
13141371

13151372
///|
@@ -1327,5 +1384,6 @@ pub fn Composer::load_wgsl_from_assets_base_required(
13271384
defines: @hashmap.HashMap::new(),
13281385
value_defines: default_wgsl_value_defines(),
13291386
redirects: [],
1387+
additional_imports: [],
13301388
})
13311389
}

composer_registry_test.mbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ test "naga_oil: add_composable_module resolves imports from the composer registr
160160
defines: @hashmap.HashMap::new(),
161161
value_defines: @moon_wgsl.default_wgsl_value_defines(),
162162
redirects: [],
163+
additional_imports: [],
163164
}) catch {
164165
err =>
165166
abort(

composer_test.mbt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fn composer_test_compose_options(
3333
defines,
3434
value_defines: @moon_wgsl.default_wgsl_value_defines(),
3535
redirects,
36+
additional_imports: [],
3637
}
3738
}
3839

@@ -334,6 +335,7 @@ test "naga_oil: composer local registry can compose without global registry stat
334335
defines: empty_bool_defines(),
335336
value_defines: @moon_wgsl.default_wgsl_value_defines(),
336337
redirects: [],
338+
additional_imports: [],
337339
}) catch {
338340
err =>
339341
abort(
@@ -383,6 +385,7 @@ test "naga_oil: add_composable_module feeds the composer compose path" {
383385
defines: empty_bool_defines(),
384386
value_defines: @moon_wgsl.default_wgsl_value_defines(),
385387
redirects: [],
388+
additional_imports: [],
386389
}) catch {
387390
err =>
388391
abort(
@@ -394,6 +397,58 @@ test "naga_oil: add_composable_module feeds the composer compose path" {
394397
debug_inspect(preprocessed.contains("return PI_2;"), content="true")
395398
}
396399

400+
///|
401+
test "naga_oil: add_composable_module applies descriptor additional imports" {
402+
@moon_wgsl.clear_registered_wgsl_source_registry()
403+
let composer = @moon_wgsl.Composer::default()
404+
composer.clear_registered_wgsl_source_registry()
405+
ignore(
406+
composer.add_composable_module({
407+
source: "#define_import_path plugin\nfn external_scale() -> f32 {\n return 2.0;\n}\n",
408+
file_path: "shaders/demo/plugin.wgsl",
409+
language: @moon_wgsl.ShaderLanguage::Wgsl,
410+
as_name: None,
411+
additional_imports: [],
412+
shader_defs: @hashmap.HashMap::new(),
413+
}),
414+
) catch {
415+
err =>
416+
abort("expected plugin module registration success: \{err.message()}")
417+
}
418+
ignore(
419+
composer.add_composable_module({
420+
source: "#define_import_path demo::main\nfn demo() -> f32 {\n return plugin::external_scale();\n}\n",
421+
file_path: "shaders/demo/main.wgsl",
422+
language: @moon_wgsl.ShaderLanguage::Wgsl,
423+
as_name: None,
424+
additional_imports: [{ import_path: "plugin", items: [] }],
425+
shader_defs: @hashmap.HashMap::new(),
426+
}),
427+
) catch {
428+
err => abort("expected main module registration success: \{err.message()}")
429+
}
430+
let preprocessed = composer.compose_wgsl("shaders/demo/main.wgsl", {
431+
assets_base: "",
432+
defines: empty_bool_defines(),
433+
value_defines: @moon_wgsl.default_wgsl_value_defines(),
434+
redirects: [],
435+
additional_imports: [],
436+
}) catch {
437+
err =>
438+
abort(
439+
"expected additional-import-backed preprocess success: \{err.message()}",
440+
)
441+
}
442+
debug_inspect(
443+
preprocessed.contains("fn plugin__external_scale"),
444+
content="true",
445+
)
446+
debug_inspect(
447+
preprocessed.contains("plugin__external_scale()"),
448+
content="true",
449+
)
450+
}
451+
397452
///|
398453
test "naga_oil: composer can register a WGSL source tree from moonbitlang/x/fs" {
399454
@moon_wgsl.clear_registered_wgsl_source_registry()
@@ -410,6 +465,7 @@ test "naga_oil: composer can register a WGSL source tree from moonbitlang/x/fs"
410465
defines: empty_bool_defines(),
411466
value_defines: @moon_wgsl.default_wgsl_value_defines(),
412467
redirects: [],
468+
additional_imports: [],
413469
}) catch {
414470
err =>
415471
abort("expected source-tree-backed preprocess success: \{err.message()}")

docs/moon_wgsl-issue-tracker.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# moon_wgsl Issue Tracker
22

3-
Last updated: 2026-04-15
3+
Last updated: 2026-04-29
44

55
## Status Legend
66

@@ -32,7 +32,19 @@ Last updated: 2026-04-15
3232
| `WGSL-017` | `moon ide analyze` architecture audit | Instance-scoped composition and package-level compatibility helpers still rely on raw registry maps instead of one explicit registry abstraction, so the global registry boundary remains implicit in core code. | `DONE` | Added a shared internal `WgslSourceRegistry` abstraction and moved both `Composer` and package-level compatibility helpers onto it, so global helpers now wrap an explicit singleton registry instead of being the core state model. Verified with `moon fmt && moon test` on 2026-04-15 (`66/66` passing), including a new regression that composer snapshots stay isolated from later global mutations. |
3333
| `WGSL-018` | `moon ide analyze` architecture audit | WGSL declaration parsing and dependency analysis are split across composer and export, so item extraction, source catalogs, and tree-shaking can drift as the language surface grows. | `DONE` | Extracted shared declaration-graph and dependency-analysis logic into `wgsl_analysis.mbt`, and routed compose-time item extraction plus export-time tree-shaking/source-map generation through the same helpers. Verified with `moon fmt && moon test` on 2026-04-15 (`66/66` passing), including a new regression that compose/export preserve the same annotated global dependencies. |
3434
| `WGSL-019` | `moon ide analyze` architecture audit | `composer.mbt` still owns most import-planning and namespace-rewrite helpers, leaving the primary orchestration file too large and too sticky for further features. | `DONE` | Split composer-specific import planning, namespace resolution, and alias-renaming helpers into `composer_imports.mbt`, leaving `composer.mbt` focused on public API, session setup, condition handling, and recursive orchestration. Verified with `moon fmt && moon test` on 2026-04-15 (`66/66` passing). |
35+
| `WGSL-020` | GitHub issue #2 | Bevy-style const declarations with trailing line comments could be merged into the following declaration and then disappear from alias rewrites/tree-shaking. | `DONE` | Fixed by treating line-comment-terminated declarations as complete declaration boundaries. Released in `0.1.3` and verified with `moon test` on 2026-04-29. |
36+
| `WGSL-021` | GitHub issue #3 | Mixed Bevy PBR import graphs exposed missing import-only entry preservation and nested alias/type rewrites for source-level composition. | `DONE` | Fixed by preserving import-only WGSL entry items and expanding the Bevy mixed full/item import regression suite. Released across `0.1.4` and `0.1.5`; verified with `moon test` on 2026-04-29. |
37+
| `WGSL-022` | GitHub issue #4 | Repeated full-module imports under a nested alias skipped cached alias redirects, leaving expressions such as `view_bindings::view` unresolved. | `DONE` | Fixed by reusing cached alias redirects when a full-module import has already been visited. Released in `0.1.6`; verified with `moon test` on 2026-04-29 (`85/85` passing). |
38+
| `WGSL-023` | Upstream naga_oil parity audit | The upstream compose test surface was only partially represented in local tests and not tracked as an explicit coverage matrix. | `DONE` | Added `docs/naga_oil-parity.md` with the upstream compose test/fixture matrix, source-level compatibility boundary, blocked cases, and the next architecture priorities. |
39+
| `WGSL-024` | Architecture review | Import syntax parsing/planning is still split between preprocessor metadata parsing and composer/module-graph import target parsing, so alias/group syntax can drift between APIs. | `TODO` | Consolidate `parse_imports` and `collect_wgsl_import_targets` around one canonical import AST/parser, then route both metadata output and recursive compose planning through that representation. |
40+
| `WGSL-025` | Upstream `additional_import` parity | Descriptor-level `additional_imports` exists, but source-level `compose_wgsl` / `export_wgsl_with_options` has no stable root-request API for injecting additional imports. | `DONE` | Added root-only `WgslComposeOptions.additional_imports`, wired descriptor-level additional imports into registered composable module source, and added compose/export regressions based on the upstream `add_imports` fixture shape. Full `virtual`/`override` overlay semantics remain part of `WGSL-012` because they require Naga. |
41+
| `WGSL-026` | Upstream `bad_identifiers` parity | Naga can sanitize invalid/reserved identifiers during IR writeback, but this source-level library currently lacks a defined identifier-sanitization layer. | `TODO` | Implement a limited source-level sanitizer for declarations/references the local analyzer can identify, and document that full parser/IR-equivalent sanitization remains outside source-only scope. |
42+
| `WGSL-027` | Upstream `invalid_override` parity | Redirect/override-style source-level APIs do not yet diagnose redirects that never match a local declaration, while upstream catches invalid override cases through Naga. | `TODO` | Add explicit redirect diagnostics, likely through export diagnostics or a checked compose API, without pretending to validate full Naga `virtual`/`override` semantics. |
43+
| `WGSL-028` | Upstream `test_shader` parity | The local parity corpus lacks a simple compute shader smoke test corresponding to upstream `compute_test.wgsl`. | `DONE` | Added `testdata/upstream_compose/compute_test` and an export smoke test that preserves the compute entry point plus imported module dependency. Verified with `moon test` on 2026-04-29. |
3544

3645
## Current work queue
3746

38-
- No active `TODO` or `IN_PROGRESS` items. `WGSL-012` remains intentionally `BLOCKED` on true Naga/runtime-backed parity coverage.
47+
- `WGSL-024`: consolidate import parsing and planning around one canonical import AST.
48+
- `WGSL-026`: add a documented source-level identifier sanitizer where local parsing is sufficient.
49+
- `WGSL-027`: add checked redirect diagnostics for source-level override/redirect usage.
50+
- `WGSL-012` remains intentionally `BLOCKED` on true Naga/runtime-backed parity coverage.

0 commit comments

Comments
 (0)