wesl 0.7.26 → 0.7.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +283 -147
  3. package/dist/index.js +1765 -1143
  4. package/package.json +2 -2
  5. package/src/AbstractElems.ts +264 -82
  6. package/src/ClickableError.ts +8 -1
  7. package/src/Linker.ts +63 -67
  8. package/src/LinkerUtil.ts +141 -7
  9. package/src/LowerAndEmit.ts +660 -304
  10. package/src/Mangler.ts +1 -1
  11. package/src/ModuleResolver.ts +15 -4
  12. package/src/ParseWESL.ts +21 -33
  13. package/src/SrcMap.ts +7 -19
  14. package/src/StandardTypes.ts +1 -1
  15. package/src/WeslDevice.ts +1 -0
  16. package/src/debug/ASTtoString.ts +92 -76
  17. package/src/index.ts +1 -1
  18. package/src/parse/AttachComments.ts +289 -0
  19. package/src/parse/ExpressionUtil.ts +3 -3
  20. package/src/parse/Keywords.ts +1 -1
  21. package/src/parse/ParseAttribute.ts +49 -71
  22. package/src/parse/ParseCall.ts +3 -4
  23. package/src/parse/ParseControlFlow.ts +100 -56
  24. package/src/parse/ParseDirective.ts +9 -8
  25. package/src/parse/ParseDoBlock.ts +63 -0
  26. package/src/parse/ParseExpression.ts +11 -10
  27. package/src/parse/ParseFn.ts +11 -33
  28. package/src/parse/ParseGlobalVar.ts +36 -27
  29. package/src/parse/ParseIdent.ts +4 -5
  30. package/src/parse/ParseLocalVar.ts +16 -12
  31. package/src/parse/ParseLoop.ts +76 -39
  32. package/src/parse/ParseModule.ts +65 -19
  33. package/src/parse/ParseSimpleStatement.ts +112 -66
  34. package/src/parse/ParseStatement.ts +40 -67
  35. package/src/parse/ParseStruct.ts +8 -22
  36. package/src/parse/ParseType.ts +10 -13
  37. package/src/parse/ParseUtil.ts +26 -12
  38. package/src/parse/ParseValueDeclaration.ts +18 -31
  39. package/src/parse/ParseWesl.ts +11 -14
  40. package/src/parse/ParsingContext.ts +11 -9
  41. package/src/parse/WeslStream.ts +138 -121
  42. package/src/parse/stream/RegexMatchers.ts +45 -0
  43. package/src/parse/stream/WeslLexer.ts +249 -0
  44. package/src/test/BevyLink.test.ts +18 -11
  45. package/src/test/ConditionalElif.test.ts +4 -2
  46. package/src/test/DeclCommentEmit.test.ts +122 -0
  47. package/src/test/DoBlock.test.ts +164 -0
  48. package/src/test/FilterValidElements.test.ts +4 -6
  49. package/src/test/Linker.test.ts +23 -7
  50. package/src/test/Mangling.test.ts +8 -4
  51. package/src/test/ParseComments.test.ts +234 -6
  52. package/src/test/ParseConditionsV2.test.ts +47 -175
  53. package/src/test/ParseElifV2.test.ts +12 -33
  54. package/src/test/ParseErrorV2.test.ts +0 -0
  55. package/src/test/ParseWeslV2.test.ts +247 -626
  56. package/src/test/StatementEmit.test.ts +143 -0
  57. package/src/test/StripWesl.ts +24 -3
  58. package/src/test/TestLink.ts +19 -3
  59. package/src/test/TestUtil.ts +18 -8
  60. package/src/test/Tokenizer.test.ts +96 -0
  61. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
  62. package/src/RawEmit.ts +0 -103
  63. package/src/Reflection.ts +0 -336
  64. package/src/TransformBindingStructs.ts +0 -320
  65. package/src/parse/ContentsHelpers.ts +0 -70
  66. package/src/parse/stream/CachingStream.ts +0 -48
  67. package/src/parse/stream/MatchersStream.ts +0 -85
@@ -11,13 +11,11 @@ const bevyBulkTest = {
11
11
  name: "Bevy",
12
12
  baseDir: "bevy-wgsl",
13
13
  git: {
14
- url: "https://github.com/wgsl-tooling-wg/bevy-wgsl.git",
14
+ url: "https://github.com/webgpu-tools/bevy-wgsl.git",
15
15
  revision: "84977ff025eaf8d92e56a9c35b815fae70eb4af0",
16
16
  },
17
17
  };
18
18
 
19
- await fetchBulkTest(bevyBulkTest, fixturesDir);
20
-
21
19
  // Constants needed by various Bevy modules
22
20
  const bevyConstants = {
23
21
  MAX_CASCADES_PER_LIGHT: 4,
@@ -36,6 +34,7 @@ const conditionalFiles: Record<string, Conditions> = {
36
34
  "./pbr/decal/clustered.wesl": { CLUSTERED_DECALS_ARE_USABLE: true },
37
35
  "./pbr/decal/forward.wesl": { DEPTH_PREPASS: true },
38
36
  "./pbr/morph.wesl": { MORPH_TARGETS: true },
37
+ "./pbr/irradiance_volume.wesl": { IRRADIANCE_VOLUMES_ARE_USABLE: true },
39
38
  };
40
39
 
41
40
  // LATER: binding_array is a WGSL extension not yet supported
@@ -44,8 +43,21 @@ const skipFiles = [
44
43
  "./pbr/decal/clustered.wesl", // imports mesh_view_bindings which uses binding_array
45
44
  ];
46
45
 
47
- // Files that only contain imports (re-export modules) - always produce empty output
48
- const importOnlyFiles = ["./sprite/mesh2d_view_types.wesl"];
46
+ // Files that produce empty WGSL with the default conditions: either pure
47
+ // re-export modules (imports only), or modules whose every top-level decl is
48
+ // behind an `@if` that is off by default (and whose only enabling condition
49
+ // pulls in an as-yet-unsupported WGSL extension, so we can't link it non-empty).
50
+ const emptyOutputFiles = [
51
+ "./sprite/mesh2d_view_types.wesl", // imports only
52
+ "./pbr/meshlet_visibility_buffer_resolve.wesl", // all decls behind @if(MESHLET_MESH_MATERIAL_PASS); enabling it needs binding_array (r64uint)
53
+ ];
54
+
55
+ await fetchBulkTest(bevyBulkTest, fixturesDir);
56
+
57
+ const weslSrc = await loadBevyBundle();
58
+ const allFiles = Object.keys(weslSrc)
59
+ .filter(f => !skipFiles.includes(f))
60
+ .sort();
49
61
 
50
62
  async function loadBevyBundle(): Promise<Record<string, string>> {
51
63
  const bevyDir = new URL(
@@ -75,11 +87,6 @@ async function loadDir(
75
87
  }
76
88
  }
77
89
 
78
- const weslSrc = await loadBevyBundle();
79
- const allFiles = Object.keys(weslSrc)
80
- .filter(f => !skipFiles.includes(f))
81
- .sort();
82
-
83
90
  allFiles.forEach(file => {
84
91
  test(`bevy: link ${file}`, async () => {
85
92
  const conditions = conditionalFiles[file] ?? {};
@@ -91,7 +98,7 @@ allFiles.forEach(file => {
91
98
  );
92
99
 
93
100
  const wgsl = result.dest;
94
- if (importOnlyFiles.includes(file)) {
101
+ if (emptyOutputFiles.includes(file)) {
95
102
  expect(wgsl).toBe("");
96
103
  } else {
97
104
  expect(wgsl.length).toBeGreaterThan(0);
@@ -84,8 +84,10 @@ test("@elif on functions", async () => {
84
84
  `;
85
85
 
86
86
  const expected = `
87
- fn compute() -> u32 { return 2; }
88
-
87
+ fn compute() -> u32 {
88
+ return 2;
89
+ }
90
+
89
91
  fn main() -> u32 {
90
92
  return compute();
91
93
  }
@@ -0,0 +1,122 @@
1
+ import { expect, test } from "vitest";
2
+ import { linkTest } from "./TestUtil.ts";
3
+ import { expectTrimmedMatch } from "./TrimmedMatch.ts";
4
+
5
+ // Comments attached to declaration-level nodes (module decls, struct members,
6
+ // fn signatures) survive linking. Statement- and expression-level comments are
7
+ // covered in StatementEmit / ParseComments.
8
+
9
+ test("doc comment above a function", async () => {
10
+ const src = `
11
+ // describe foo
12
+ fn foo() { }
13
+ `;
14
+ const result = await linkTest(src);
15
+ expectTrimmedMatch(result, src);
16
+ });
17
+
18
+ test("doc comment above a struct", async () => {
19
+ const src = `
20
+ // a point
21
+ struct Point { x: i32 }
22
+ `;
23
+ const result = await linkTest(src);
24
+ expect(result).toContain("// a point");
25
+ });
26
+
27
+ test("doc comment above a global const", async () => {
28
+ const src = `
29
+ // the answer
30
+ const answer = 42;
31
+ `;
32
+ const result = await linkTest(src);
33
+ expectTrimmedMatch(result, src);
34
+ });
35
+
36
+ test("trailing comment on a global const", async () => {
37
+ const src = `const answer = 42; // the answer`;
38
+ const result = await linkTest(src);
39
+ expectTrimmedMatch(result, src);
40
+ });
41
+
42
+ test("leading comment on a struct member", async () => {
43
+ const src = `
44
+ struct S {
45
+ // the x field
46
+ x: i32,
47
+ y: i32,
48
+ }
49
+ `;
50
+ const result = await linkTest(src);
51
+ expectTrimmedMatch(result, src);
52
+ });
53
+
54
+ test("trailing comment on a struct member", async () => {
55
+ const src = `
56
+ struct S {
57
+ x: i32, // horizontal
58
+ y: i32,
59
+ }
60
+ `;
61
+ const result = await linkTest(src);
62
+ expect(result).toContain("// horizontal");
63
+ });
64
+
65
+ test("comment on a sole struct member forces the multi-line form", async () => {
66
+ const src = `struct S { /* the field */ x: i32 }`;
67
+ const result = await linkTest(src);
68
+ expect(result).toContain("/* the field */");
69
+ expect(result).toContain("x: i32");
70
+ });
71
+
72
+ test("comment leading a function parameter", async () => {
73
+ const src = `fn foo(/* count */ n: i32) { }`;
74
+ const result = await linkTest(src);
75
+ expectTrimmedMatch(result, src);
76
+ });
77
+
78
+ test("comment trailing a function parameter", async () => {
79
+ const src = `fn foo(n: i32 /* count */) { }`;
80
+ const result = await linkTest(src);
81
+ expectTrimmedMatch(result, src);
82
+ });
83
+
84
+ test("comment before a function name", async () => {
85
+ const src = `fn /* the entry point */ main() { }`;
86
+ const result = await linkTest(src);
87
+ expectTrimmedMatch(result, src);
88
+ });
89
+
90
+ test("comment before a struct name", async () => {
91
+ const src = `struct /* a point */ Point { x: i32 }`;
92
+ const result = await linkTest(src);
93
+ expect(result).toContain("/* a point */");
94
+ });
95
+
96
+ test("comment before an alias name", async () => {
97
+ const src = `alias /* small int */ Byte = i32;`;
98
+ const result = await linkTest(src);
99
+ expect(result).toContain("/* small int */");
100
+ });
101
+
102
+ test("comment before a return type", async () => {
103
+ const src = `fn one() -> /* always one */ i32 { return 1; }`;
104
+ const result = await linkTest(src);
105
+ expect(result).toContain("/* always one */");
106
+ });
107
+
108
+ test("comment trailing an attribute", async () => {
109
+ const src = `@compute /* on the gpu */ @workgroup_size(1) fn main() { }`;
110
+ const result = await linkTest(src);
111
+ expect(result).toContain("/* on the gpu */");
112
+ });
113
+
114
+ test("comment before a local declaration name", async () => {
115
+ const src = `
116
+ fn main() {
117
+ let /* result */ x = 1;
118
+ }
119
+ `;
120
+ const result = await linkTest(src);
121
+ expect(result).toContain("/* result */");
122
+ });
@@ -0,0 +1,164 @@
1
+ import { expect, test } from "vitest";
2
+ import type { CallElem, DoBlockElem } from "../AbstractElems.ts";
3
+ import { freshResolver, RecordResolver } from "../ModuleResolver.ts";
4
+ import { linkTestOpts, parseTest } from "./TestUtil.ts";
5
+
6
+ /** `do` blocks are an opt-in WESL extension (default off). */
7
+ const doExt = { weslExtensions: { doBlocks: true } } as const;
8
+
9
+ const example1 = `
10
+ @buffer var<storage, read_write> grid: array<f32, 64>;
11
+
12
+ @compute @workgroup_size(8)
13
+ fn jacobi_step(@builtin(global_invocation_id) id: vec3u) {
14
+ let i = id.x;
15
+ if (i > 0u && i < 63u) { grid[i] = (grid[i - 1u] + grid[i + 1u]) * 0.5; }
16
+ }
17
+
18
+ @test
19
+ do test_jacobi() {
20
+ for (var i = 0u; i < 100u; i++) { jacobi_step(8, 1, 1); }
21
+ }
22
+ `;
23
+
24
+ const example2 = `
25
+ do reduce(count: u32) {
26
+ let groups = count / 256u;
27
+ if groups > 1u {
28
+ reduce_step(groups, 1, 1);
29
+ reduce(groups);
30
+ } else {
31
+ reduce_step(1, 1, 1);
32
+ }
33
+ }
34
+
35
+ do frame(u: Uniforms, @slider(1, 100) steps: u32) {
36
+ if u.frame == 0u { init_min_max(1, 1, 1); }
37
+ for (var i = 0u; i < steps; i++) {
38
+ step_sim(u.resolution.x / 8u, u.resolution.y / 8u, 1);
39
+ }
40
+ render();
41
+ }
42
+ `;
43
+
44
+ function doBlocks(src: string): DoBlockElem[] {
45
+ const ast = parseTest(src, doExt);
46
+ return ast.moduleElem.decls.filter((e): e is DoBlockElem => e.kind === "do");
47
+ }
48
+
49
+ test("parse do block: name, params, control flow body", () => {
50
+ const blocks = doBlocks(example1);
51
+ expect(blocks).toHaveLength(1);
52
+ const [block] = blocks;
53
+ expect(block.name.name).toBe("test_jacobi");
54
+ expect(block.params).toHaveLength(0);
55
+ expect(block.body.body.length).toBeGreaterThan(0);
56
+ });
57
+
58
+ test("do body call-expressions expose structured .arguments", () => {
59
+ // Expressions are first-class AST nodes, so call args are typed ExpressionElem
60
+ // trees - the interpreter reads them directly.
61
+ const src = `
62
+ do tick() {
63
+ fill(1, 1, 1);
64
+ }
65
+ `;
66
+ const [block] = doBlocks(src);
67
+ const stmt = block.body.body.find((c): c is CallElem => c.kind === "call");
68
+ expect(stmt).toBeDefined();
69
+ const call = stmt!.call;
70
+ expect(call.arguments).toHaveLength(3);
71
+ expect(call.arguments.every(a => a.kind === "literal")).toBe(true);
72
+ });
73
+
74
+ test("parse do blocks: recursion, if/else, uniforms + slider params", () => {
75
+ const blocks = doBlocks(example2);
76
+ expect(blocks.map(b => b.name.name)).toEqual(["reduce", "frame"]);
77
+
78
+ const [reduce, frame] = blocks;
79
+ expect(reduce.params).toHaveLength(1);
80
+ expect(reduce.params[0].name.decl.ident.originalName).toBe("count");
81
+
82
+ expect(frame.params).toHaveLength(2);
83
+ expect(frame.params.map(p => p.name.decl.ident.originalName)).toEqual([
84
+ "u",
85
+ "steps",
86
+ ]);
87
+ expect(frame.body.body.length).toBeGreaterThan(0);
88
+ });
89
+
90
+ test("link drops the do block, keeps surrounding declarations", async () => {
91
+ const wgsl = await linkTestOpts(doExt, example1);
92
+ expect(wgsl).toContain("fn jacobi_step");
93
+ expect(wgsl).toContain("grid");
94
+ expect(wgsl).toContain("@compute");
95
+ expect(wgsl).not.toContain("test_jacobi");
96
+ expect(wgsl).not.toMatch(/(^|\s)do\s/);
97
+ });
98
+
99
+ test("do block body links without spurious binding errors", async () => {
100
+ // example2 has no fn/global decls, only do blocks: a clean emit proves the
101
+ // do bodies (u.frame, steps, step_sim, render, reduce) bypass bindIdents.
102
+ const wgsl = await linkTestOpts(doExt, example2);
103
+ for (const name of [
104
+ "reduce",
105
+ "frame",
106
+ "step_sim",
107
+ "render",
108
+ "Uniforms",
109
+ "init_min_max",
110
+ ]) {
111
+ expect(wgsl).not.toContain(name);
112
+ }
113
+ });
114
+
115
+ test("error: fn and do block with the same name", () => {
116
+ const src = `
117
+ fn reduce() {}
118
+ do reduce() {}
119
+ `;
120
+ expect(() => parseTest(src, doExt)).toThrow(/declared as both fn and do/);
121
+ });
122
+
123
+ test("error: two do blocks with the same name", () => {
124
+ // runners key do blocks by name, so a duplicate would silently shadow the
125
+ // first; reject it at parse time instead of running the wrong block.
126
+ const src = `
127
+ do same() {}
128
+ do same() {}
129
+ `;
130
+ expect(() => parseTest(src, doExt)).toThrow(/declared as do more than once/);
131
+ });
132
+
133
+ test("default off: do block syntax is not recognized without the extension", () => {
134
+ // Without the extension, `do` stays a reserved word and module-level `do`
135
+ // fails to parse (no DoBlockElem is produced).
136
+ expect(() => parseTest(example1)).toThrow();
137
+ expect(() => parseTest("do tick() {}")).toThrow();
138
+ });
139
+
140
+ test("freshResolver preserves weslExtensions across re-parse", () => {
141
+ // Resolver reuse re-parses via parseSrcModule(ast.srcModule, ast.parseOptions);
142
+ // the do-block extension must survive that round trip.
143
+ const src = "do tick() {}";
144
+ const inner = new RecordResolver({ "package::main": src }, doExt);
145
+ const wrapped = freshResolver(freshResolver(inner));
146
+ const ast = wrapped.resolveModule("package::main")!;
147
+ const blocks = ast.moduleElem.decls.filter(
148
+ (e): e is DoBlockElem => e.kind === "do",
149
+ );
150
+ expect(blocks.map(b => b.name.name)).toEqual(["tick"]);
151
+
152
+ // an optionless resolver stays OFF (fails to parse `do`, as before)
153
+ const off = freshResolver(new RecordResolver({ "package::main": src }));
154
+ expect(() => off.resolveModule("package::main")).toThrow();
155
+ });
156
+
157
+ test("regression: a normal fn named do_something is unaffected", async () => {
158
+ const wgsl = await linkTestOpts({}, "fn do_something() {}");
159
+ expect(wgsl).toContain("fn do_something");
160
+ });
161
+
162
+ test("regression: do as a reserved word still errors when the extension is off", () => {
163
+ expect(() => parseTest("fn f() { do }")).toThrow(/Expected ';'/);
164
+ });
@@ -21,7 +21,6 @@ function mockElem(testId: string, attributes?: AttributeElem[]): TestElem {
21
21
  kind: "alias",
22
22
  testId,
23
23
  attributes,
24
- contents: [],
25
24
  start: 0,
26
25
  end: 0,
27
26
  } as TestElem;
@@ -31,13 +30,15 @@ function ifAttr(condition: boolean): AttributeElem {
31
30
  const literal: Literal = {
32
31
  kind: "literal",
33
32
  value: condition ? "true" : "false",
34
- span: [0, 0],
33
+ start: 0,
34
+ end: 0,
35
35
  };
36
36
 
37
37
  const translateTimeExpr: TranslateTimeExpressionElem = {
38
38
  kind: "translate-time-expression",
39
39
  expression: literal,
40
- span: [0, 0],
40
+ start: 0,
41
+ end: 0,
41
42
  };
42
43
 
43
44
  const ifAttribute: IfAttribute = {
@@ -48,7 +49,6 @@ function ifAttr(condition: boolean): AttributeElem {
48
49
  return {
49
50
  kind: "attribute",
50
51
  attribute: ifAttribute,
51
- contents: [],
52
52
  start: 0,
53
53
  end: 0,
54
54
  };
@@ -62,7 +62,6 @@ function elseAttr(): AttributeElem {
62
62
  return {
63
63
  kind: "attribute",
64
64
  attribute: elseAttribute,
65
- contents: [],
66
65
  start: 0,
67
66
  end: 0,
68
67
  };
@@ -175,7 +174,6 @@ test("complex nested attributes", () => {
175
174
  const standardAttr = (name: string): AttributeElem => ({
176
175
  kind: "attribute",
177
176
  attribute: { kind: "@attribute", name, params: [] },
178
- contents: [],
179
177
  start: 0,
180
178
  end: 0,
181
179
  });
@@ -14,6 +14,12 @@ test("global diagnostic directive", async () => {
14
14
  expectTrimmedMatch(result, src);
15
15
  });
16
16
 
17
+ test("consecutive top-level directives stay on separate lines", async () => {
18
+ const src = `requires readonly_and_readwrite_storage_textures;\nenable f16;`;
19
+ const result = await linkTest(src);
20
+ expectTrimmedMatch(result, src);
21
+ });
22
+
17
23
  test("@diagnostic attribute on statement", async () => {
18
24
  const src = `fn foo() { @diagnostic(info, derivative_uniformity) if true { } }`;
19
25
  const result = await linkTest(src);
@@ -54,7 +60,9 @@ test("link an alias", async () => {
54
60
  const src = /* wgsl */ `
55
61
  alias Num = f32;
56
62
 
57
- fn main() { Num(1.0); }
63
+ fn main() {
64
+ Num(1.0);
65
+ }
58
66
  `;
59
67
  const result = await linkTest(src);
60
68
  expectTrimmedMatch(result, src);
@@ -112,9 +120,9 @@ test("type inside fn with same name as fn", async () => {
112
120
  // illegal but shouldn't hang
113
121
  const src = `
114
122
  fn foo() {
115
- var a:foo;
123
+ var a: foo;
116
124
  }
117
- fn bar() {}
125
+ fn bar() { }
118
126
  `;
119
127
  const result = await linkTest(src);
120
128
  expectTrimmedMatch(result, src);
@@ -183,8 +191,12 @@ test("cross-module @location attribute const", async () => {
183
191
  `;
184
192
  const result = await linkTest(main, file1);
185
193
  const expected = `
186
- fn main() { let x = foo(); }
187
- fn foo() -> @location(loc) vec4f { return vec4f(); }
194
+ fn main() {
195
+ let x = foo();
196
+ }
197
+ fn foo() -> @location(loc) vec4f {
198
+ return vec4f();
199
+ }
188
200
  const loc = 0;
189
201
  `;
190
202
  expectTrimmedMatch(result, expected);
@@ -207,7 +219,9 @@ test("link with library bundle (tests CompositeResolver)", async () => {
207
219
  ];
208
220
  const result = await linkTestOpts({ libs }, main);
209
221
  const expected = `
210
- fn main() { helper(); }
222
+ fn main() {
223
+ helper();
224
+ }
211
225
  fn helper() { }
212
226
  `;
213
227
  expectTrimmedMatch(result, expected);
@@ -221,7 +235,9 @@ test("inline ref in array size from another module", async () => {
221
235
  const file1 = `const SIZE = 4;`;
222
236
  const result = await linkTest(main, file1);
223
237
  const expected = `
224
- fn main() { var a: array<f32, SIZE>; }
238
+ fn main() {
239
+ var a: array<f32, SIZE>;
240
+ }
225
241
  const SIZE = 4;
226
242
  `;
227
243
  expectTrimmedMatch(result, expected);
@@ -15,8 +15,10 @@ fn main() { bar(); }
15
15
 
16
16
  const linked = await linkTestOpts({ mangler: underscoreMangle }, main, file1);
17
17
  const expected = `
18
- fn main() { package_file1_bar(); }
19
- fn package_file1_bar() {}
18
+ fn main() {
19
+ package_file1_bar();
20
+ }
21
+ fn package_file1_bar() { }
20
22
  `;
21
23
  expectTrimmedMatch(linked, expected);
22
24
  });
@@ -38,8 +40,10 @@ fn main() { bar(); }
38
40
  });
39
41
 
40
42
  const expected = `
41
- fn main() { package_container_file1_bar(); }
42
- fn package_container_file1_bar() {}
43
+ fn main() {
44
+ package_container_file1_bar();
45
+ }
46
+ fn package_container_file1_bar() { }
43
47
  `;
44
48
  expectTrimmedMatch(linked.dest, expected);
45
49
  });