wesl 0.7.0 → 0.7.2

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.
package/dist/index.d.ts CHANGED
@@ -104,6 +104,8 @@ interface RefIdent extends IdentBase {
104
104
  std?: true;
105
105
  /** True for identifiers in @if/@elif conditions. Binding skips these (for now). */
106
106
  conditionRef?: true;
107
+ /** Attribute name if this ref is inside an attribute param (for skip-binding check). */
108
+ attrParam?: string;
107
109
  ast: WeslAST;
108
110
  refIdentElem: RefIdentElem;
109
111
  }
@@ -1003,24 +1005,19 @@ declare function srcLog(src: string | SrcMap, pos: number | [number, number], ..
1003
1005
  /** WESL module path utilities for converting between :: and / separators. */
1004
1006
  /**
1005
1007
  * Convert module path segments to relative file path.
1006
- * Handles package/packageName prefixes and super:: resolution.
1008
+ * Resolves package:: and super:: via srcModuleParts context.
1007
1009
  *
1008
- * @param parts - module path as array e.g., ["package", "utils", "helper"]
1009
- * @param packageName - the current package's name (required)
1010
- * @param srcModuleParts - source module path for super:: resolution (optional)
1011
1010
  * @returns relative file path e.g., "utils/helper", or undefined if external
1012
1011
  */
1013
1012
  declare function modulePartsToRelativePath(parts: string[], packageName: string, srcModuleParts?: string[]): string | undefined;
1014
1013
  /** String variant of modulePartsToRelativePath. */
1015
1014
  declare function moduleToRelativePath(modulePath: string, packageName: string, srcModulePath?: string): string | undefined;
1016
1015
  /**
1017
- * Resolve super:: elements to absolute module path.
1018
- *
1019
- * @param parts - module path with potential super:: elements
1020
- * @param srcModuleParts - source module path for context
1021
- * @returns absolute module path parts (no super:: elements)
1016
+ * Resolve package:: and super:: to absolute module path.
1017
+ * - package:: replaced with actual package name from source module
1018
+ * - super:: climbs up the module path hierarchy
1022
1019
  */
1023
- declare function resolveSuper(parts: string[], srcModuleParts: string[]): string[];
1020
+ declare function resolveModulePath(parts: string[], srcModuleParts: string[]): string[];
1024
1021
  /** Normalize debug root to end with / or be empty. */
1025
1022
  declare function normalizeDebugRoot(root?: string): string;
1026
1023
  //#endregion
@@ -1200,4 +1197,4 @@ declare function offsetToLineNumber(offset: number, text: string): [lineNum: num
1200
1197
  */
1201
1198
  declare function errorHighlight(source: string, span: Span): [string, string];
1202
1199
  //#endregion
1203
- export { AbstractElem, AbstractElemBase, AliasElem, Attribute, AttributeElem, BatchModuleResolver, BinaryExpression, BinaryOperator, BindIdentsParams, BindResults, BindingAST, BindingStructElem, BlockStatement, BoundAndTransformed, BuiltinAttribute, BundleResolver, ComponentExpression, ComponentMemberExpression, CompositeResolver, ConditionalAttribute, Conditions, ConstAssertElem, ConstElem, ContainerElem, ContinuingElem, DeclIdent, DeclIdentElem, DeclarationElem, DiagnosticAttribute, DiagnosticDirective, DiagnosticRule, DirectiveElem, DirectiveVariant, ElemKindMap, ElemWithAttributes, ElemWithContentsBase, ElifAttribute, ElseAttribute, EmittableElem, EnableDirective, ExpressionElem, ExtendedGPUValidationError, FnElem, FnParamElem, FunctionCallExpression, GlobalDeclarationElem, GlobalVarElem, GrammarElem, HasAttributes, Ident, IfAttribute, ImportCollection, ImportElem, ImportItem, ImportSegment, ImportStatement, InterpolateAttribute, LetElem, LexicalScope, LinkConfig, LinkParams, LinkRegistryParams, LinkedWesl, LinkerTransform, Literal, LiveDecls, ManglerFn, ModuleElem, ModuleResolver, NameElem, OpenElem, OverrideElem, ParenthesizedExpression, ParseError, PartialScope, RecordResolver, RecordResolverOptions, RefIdent, RefIdentElem, RequiresDirective, Scope, SimpleMemberRef, Span, SrcMap, SrcMapBuilder, SrcMapEntry, SrcModule, SrcPosition, SrcWithPath, StableState, StandardAttribute, StatementElem, StructElem, StructMemberElem, StuffElem, SwitchClauseElem, SyntheticElem, TerminalElem, TextElem, TransformedAST, TranslateTimeExpressionElem, TypeRefElem, TypeTemplateParameter, TypedDeclElem, UnaryExpression, UnaryOperator, UnknownExpressionElem, VarElem, VirtualLibrary, VirtualLibraryFn, VirtualLibrarySet, WeslAST, WeslBundle, WeslDevice, WeslGPUCompilationInfo, WeslGPUCompilationMessage, WeslJsPlugin, WeslParseContext, WeslParseError, WeslParseState, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, emptyScope, errorHighlight, fileToModulePath, filterMap, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveSuper, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, transformBindingReference, transformBindingStruct, underscoreMangle, validation, withLoggerAsync };
1200
+ export { AbstractElem, AbstractElemBase, AliasElem, Attribute, AttributeElem, BatchModuleResolver, BinaryExpression, BinaryOperator, BindIdentsParams, BindResults, BindingAST, BindingStructElem, BlockStatement, BoundAndTransformed, BuiltinAttribute, BundleResolver, ComponentExpression, ComponentMemberExpression, CompositeResolver, ConditionalAttribute, Conditions, ConstAssertElem, ConstElem, ContainerElem, ContinuingElem, DeclIdent, DeclIdentElem, DeclarationElem, DiagnosticAttribute, DiagnosticDirective, DiagnosticRule, DirectiveElem, DirectiveVariant, ElemKindMap, ElemWithAttributes, ElemWithContentsBase, ElifAttribute, ElseAttribute, EmittableElem, EnableDirective, ExpressionElem, ExtendedGPUValidationError, FnElem, FnParamElem, FunctionCallExpression, GlobalDeclarationElem, GlobalVarElem, GrammarElem, HasAttributes, Ident, IfAttribute, ImportCollection, ImportElem, ImportItem, ImportSegment, ImportStatement, InterpolateAttribute, LetElem, LexicalScope, LinkConfig, LinkParams, LinkRegistryParams, LinkedWesl, LinkerTransform, Literal, LiveDecls, ManglerFn, ModuleElem, ModuleResolver, NameElem, OpenElem, OverrideElem, ParenthesizedExpression, ParseError, PartialScope, RecordResolver, RecordResolverOptions, RefIdent, RefIdentElem, RequiresDirective, Scope, SimpleMemberRef, Span, SrcMap, SrcMapBuilder, SrcMapEntry, SrcModule, SrcPosition, SrcWithPath, StableState, StandardAttribute, StatementElem, StructElem, StructMemberElem, StuffElem, SwitchClauseElem, SyntheticElem, TerminalElem, TextElem, TransformedAST, TranslateTimeExpressionElem, TypeRefElem, TypeTemplateParameter, TypedDeclElem, UnaryExpression, UnaryOperator, UnknownExpressionElem, VarElem, VirtualLibrary, VirtualLibraryFn, VirtualLibrarySet, WeslAST, WeslBundle, WeslDevice, WeslGPUCompilationInfo, WeslGPUCompilationMessage, WeslJsPlugin, WeslParseContext, WeslParseError, WeslParseState, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, emptyScope, errorHighlight, fileToModulePath, filterMap, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, transformBindingReference, transformBindingStruct, underscoreMangle, validation, withLoggerAsync };
package/dist/index.js CHANGED
@@ -283,10 +283,11 @@ function encode_integer(num) {
283
283
 
284
284
  //#endregion
285
285
  //#region src/ClickableError.ts
286
+ const isBrowser = "document" in globalThis;
286
287
  /** Throw an error with an embedded source map so that browser users can
287
288
  * click on the error in the browser debug console and see the wesl source code. */
288
289
  function throwClickableError(params) {
289
- if (!debug) throw params.error;
290
+ if (!debug || !isBrowser) throw params.error;
290
291
  const { url, text, lineNumber, lineColumn, length, error } = params;
291
292
  const mappings = encodeVlq([
292
293
  0,
@@ -496,6 +497,25 @@ function childIdent(child) {
496
497
 
497
498
  //#endregion
498
499
  //#region src/LowerAndEmit.ts
500
+ /** Valid WGSL standard attributes (from spec). Non-WGSL attributes are stripped.
501
+ * See: https://www.w3.org/TR/WGSL/#attributes
502
+ * Note: @builtin, @diagnostic, @interpolate are parsed as separate attribute types. */
503
+ const wgslStandardAttributes$1 = new Set([
504
+ "align",
505
+ "binding",
506
+ "blend_src",
507
+ "compute",
508
+ "const",
509
+ "fragment",
510
+ "group",
511
+ "id",
512
+ "invariant",
513
+ "location",
514
+ "must_use",
515
+ "size",
516
+ "vertex",
517
+ "workgroup_size"
518
+ ]);
499
519
  /** Traverse the AST, starting from root elements, emitting WGSL for each. */
500
520
  function lowerAndEmit(params) {
501
521
  const { srcBuilder, rootElems, conditions } = params;
@@ -505,9 +525,8 @@ function lowerAndEmit(params) {
505
525
  srcBuilder,
506
526
  extracting
507
527
  };
508
- (skipConditionalFiltering ? rootElems : filterValidElements(rootElems, conditions)).forEach((e) => {
509
- lowerAndEmitElem(e, emitContext);
510
- });
528
+ const validElements = skipConditionalFiltering ? rootElems : filterValidElements(rootElems, conditions);
529
+ for (const e of validElements) lowerAndEmitElem(e, emitContext);
511
530
  }
512
531
  function lowerAndEmitElem(e, ctx) {
513
532
  switch (e.kind) {
@@ -682,9 +701,8 @@ function emitSynthetic(e, ctx) {
682
701
  ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
683
702
  }
684
703
  function emitContents(elem, ctx) {
685
- filterValidElements(elem.contents, ctx.conditions).forEach((e) => {
686
- lowerAndEmitElem(e, ctx);
687
- });
704
+ const validElements = filterValidElements(elem.contents, ctx.conditions);
705
+ for (const e of validElements) lowerAndEmitElem(e, ctx);
688
706
  }
689
707
  /** Emit contents with leading/trailing whitespace trimming (V2 parser). */
690
708
  function emitContentsWithTrimming(elem, ctx) {
@@ -777,6 +795,7 @@ function emitAttribute(e, ctx) {
777
795
  const { kind } = e.attribute;
778
796
  if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
779
797
  if (kind === "@attribute") {
798
+ if (!wgslStandardAttributes$1.has(e.attribute.name)) return false;
780
799
  emitStandardAttribute(e, ctx);
781
800
  return true;
782
801
  }
@@ -816,18 +835,25 @@ function diagnosticControlToString(severity, rule) {
816
835
  }
817
836
  function expressionToString(elem) {
818
837
  const { kind } = elem;
819
- if (kind === "binary-expression") return `${expressionToString(elem.left)} ${elem.operator.value} ${expressionToString(elem.right)}`;
820
- else if (kind === "unary-expression") return `${elem.operator.value}${expressionToString(elem.expression)}`;
821
- else if (kind === "ref") return elem.ident.originalName;
822
- else if (kind === "literal") return elem.value;
823
- else if (kind === "parenthesized-expression") return `(${expressionToString(elem.expression)})`;
824
- else if (kind === "component-expression") return `${expressionToString(elem.base)}[${elem.access}]`;
825
- else if (kind === "component-member-expression") return `${expressionToString(elem.base)}.${elem.access}`;
826
- else if (kind === "call-expression") {
827
- const fn = elem.function;
828
- return `${fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName}${elem.templateArgs ? `<...>` : ""}(${elem.arguments.map(expressionToString).join(", ")})`;
829
- } else if (kind === "type") return elem.name.originalName;
830
- else assertUnreachable(kind);
838
+ switch (kind) {
839
+ case "binary-expression": {
840
+ const left = expressionToString(elem.left);
841
+ const right = expressionToString(elem.right);
842
+ return `${left} ${elem.operator.value} ${right}`;
843
+ }
844
+ case "unary-expression": return `${elem.operator.value}${expressionToString(elem.expression)}`;
845
+ case "ref": return elem.ident.originalName;
846
+ case "literal": return elem.value;
847
+ case "parenthesized-expression": return `(${expressionToString(elem.expression)})`;
848
+ case "component-expression": return `${expressionToString(elem.base)}[${elem.access}]`;
849
+ case "component-member-expression": return `${expressionToString(elem.base)}.${elem.access}`;
850
+ case "call-expression": {
851
+ const fn = elem.function;
852
+ return `${fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName}${elem.templateArgs ? `<...>` : ""}(${elem.arguments.map(expressionToString).join(", ")})`;
853
+ }
854
+ case "type": return elem.name.originalName;
855
+ default: assertUnreachable(kind);
856
+ }
831
857
  }
832
858
  function emitDirective(e, ctx) {
833
859
  const { directive } = e;
@@ -1197,15 +1223,12 @@ function minimallyMangledName(proposedName, globalNames) {
1197
1223
  /** WESL module path utilities for converting between :: and / separators. */
1198
1224
  /**
1199
1225
  * Convert module path segments to relative file path.
1200
- * Handles package/packageName prefixes and super:: resolution.
1226
+ * Resolves package:: and super:: via srcModuleParts context.
1201
1227
  *
1202
- * @param parts - module path as array e.g., ["package", "utils", "helper"]
1203
- * @param packageName - the current package's name (required)
1204
- * @param srcModuleParts - source module path for super:: resolution (optional)
1205
1228
  * @returns relative file path e.g., "utils/helper", or undefined if external
1206
1229
  */
1207
1230
  function modulePartsToRelativePath(parts, packageName, srcModuleParts) {
1208
- const resolved = srcModuleParts ? resolveSuper(parts, srcModuleParts) : parts;
1231
+ const resolved = srcModuleParts ? resolveModulePath(parts, srcModuleParts) : parts;
1209
1232
  const rootSegment = resolved[0];
1210
1233
  if (rootSegment === "package" || rootSegment === packageName) return resolved.slice(1).join("/");
1211
1234
  }
@@ -1215,16 +1238,15 @@ function moduleToRelativePath(modulePath, packageName, srcModulePath) {
1215
1238
  return modulePartsToRelativePath(modulePath.split("::"), packageName, srcParts);
1216
1239
  }
1217
1240
  /**
1218
- * Resolve super:: elements to absolute module path.
1219
- *
1220
- * @param parts - module path with potential super:: elements
1221
- * @param srcModuleParts - source module path for context
1222
- * @returns absolute module path parts (no super:: elements)
1241
+ * Resolve package:: and super:: to absolute module path.
1242
+ * - package:: replaced with actual package name from source module
1243
+ * - super:: climbs up the module path hierarchy
1223
1244
  */
1224
- function resolveSuper(parts, srcModuleParts) {
1225
- const lastSuper = parts.lastIndexOf("super");
1226
- if (lastSuper === -1) return parts;
1227
- return [...srcModuleParts.slice(0, -(lastSuper + 1)), ...parts.slice(lastSuper + 1)];
1245
+ function resolveModulePath(parts, srcModuleParts) {
1246
+ const resolved = parts[0] === "package" ? [srcModuleParts[0], ...parts.slice(1)] : parts;
1247
+ const lastSuper = resolved.lastIndexOf("super");
1248
+ if (lastSuper === -1) return resolved;
1249
+ return [...srcModuleParts.slice(0, -(lastSuper + 1)), ...resolved.slice(lastSuper + 1)];
1228
1250
  }
1229
1251
  /** Normalize debug root to end with / or be empty. */
1230
1252
  function normalizeDebugRoot(root) {
@@ -1992,7 +2014,9 @@ function parseStandardAttribute(ctx) {
1992
2014
  if (name === "diagnostic") return parseDiagnosticAttribute(ctx, startPos);
1993
2015
  let params;
1994
2016
  if (stream.matchText("(")) {
2017
+ ctx.parsingAttrParam = name;
1995
2018
  params = parseAttributeParams(ctx);
2019
+ ctx.parsingAttrParam = void 0;
1996
2020
  expect(stream, ")", "attribute parameters");
1997
2021
  }
1998
2022
  if (name === "must_use" && params !== void 0) throw new ParseError("@must_use does not accept parameters", [startPos, stream.checkpoint()]);
@@ -3097,14 +3121,18 @@ var ParsingContext = class {
3097
3121
  while (scope.kind === "partial" && scope.parent) scope = scope.parent;
3098
3122
  return scope.parent === null;
3099
3123
  }
3124
+ /** Attribute name being parsed (for marking refs in attr params). */
3125
+ parsingAttrParam;
3100
3126
  createRefIdent(name) {
3101
- return {
3127
+ const ref = {
3102
3128
  kind: "ref",
3103
3129
  originalName: name,
3104
3130
  ast: this.state.stable,
3105
3131
  id: nextIdentId(),
3106
3132
  refIdentElem: null
3107
3133
  };
3134
+ if (this.parsingAttrParam) ref.attrParam = this.parsingAttrParam;
3135
+ return ref;
3108
3136
  }
3109
3137
  createDeclIdent(name, isGlobal = false) {
3110
3138
  return {
@@ -3635,6 +3663,23 @@ function stdEnumerant(name) {
3635
3663
 
3636
3664
  //#endregion
3637
3665
  //#region src/BindIdents.ts
3666
+ /** WGSL standard attributes whose params need binding (e.g., @workgroup_size). */
3667
+ const wgslStandardAttributes = new Set([
3668
+ "align",
3669
+ "binding",
3670
+ "blend_src",
3671
+ "compute",
3672
+ "const",
3673
+ "fragment",
3674
+ "group",
3675
+ "id",
3676
+ "invariant",
3677
+ "location",
3678
+ "must_use",
3679
+ "size",
3680
+ "vertex",
3681
+ "workgroup_size"
3682
+ ]);
3638
3683
  /** Bind ref idents to declarations and mangle global declaration names. */
3639
3684
  function bindIdents(params) {
3640
3685
  const { rootAst, resolver, virtuals, accumulateUnbound } = params;
@@ -3750,6 +3795,7 @@ function processDependentScope(decl, ctx) {
3750
3795
  function handleRef(ident$1, liveDecls, bindContext) {
3751
3796
  if (ident$1.refersTo || ident$1.std) return;
3752
3797
  if (ident$1.conditionRef) return;
3798
+ if (ident$1.attrParam && !wgslStandardAttributes.has(ident$1.attrParam)) return;
3753
3799
  const foundDecl = findDeclInModule(ident$1, liveDecls) ?? findQualifiedImport(ident$1, bindContext);
3754
3800
  if (foundDecl) {
3755
3801
  ident$1.refersTo = foundDecl.decl;
@@ -3811,7 +3857,7 @@ function matchingImport(identParts, imports) {
3811
3857
  /** @return an exported root declIdent for the provided path. */
3812
3858
  function findExport(pathParts, srcModule, ctx) {
3813
3859
  const { resolver, conditions, virtuals } = ctx;
3814
- const modulePath = resolveSuper(pathParts, srcModule.modulePath.split("::")).slice(0, -1).join("::");
3860
+ const modulePath = resolveModulePath(pathParts, srcModule.modulePath.split("::")).slice(0, -1).join("::");
3815
3861
  const moduleAst = resolver.resolveModule(modulePath) ?? virtualModule(pathParts[0], conditions, virtuals);
3816
3862
  if (!moduleAst) return void 0;
3817
3863
  const decl = publicDecl(moduleAst.rootScope, last(pathParts), conditions);
@@ -4883,4 +4929,4 @@ function makeWeslDevice(device) {
4883
4929
  }
4884
4930
 
4885
4931
  //#endregion
4886
- export { BundleResolver, CompositeResolver, LinkedWesl, ParseError, RecordResolver, SrcMap, SrcMapBuilder, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, emptyScope, errorHighlight, fileToModulePath, filterMap, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveSuper, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, transformBindingReference, transformBindingStruct, underscoreMangle, validation, withLoggerAsync };
4932
+ export { BundleResolver, CompositeResolver, LinkedWesl, ParseError, RecordResolver, SrcMap, SrcMapBuilder, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, emptyScope, errorHighlight, fileToModulePath, filterMap, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, transformBindingReference, transformBindingStruct, underscoreMangle, validation, withLoggerAsync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wesl",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
package/src/BindIdents.ts CHANGED
@@ -7,7 +7,7 @@ import type { FlatImport } from "./FlattenTreeImport.ts";
7
7
  import type { LinkRegistryParams, VirtualLibraryFn } from "./Linker.ts";
8
8
  import { type LiveDecls, makeLiveDecls } from "./LiveDeclarations.ts";
9
9
  import { type ManglerFn, minimalMangle } from "./Mangler.ts";
10
- import { resolveSuper } from "./ModulePathUtil.ts";
10
+ import { resolveModulePath } from "./ModulePathUtil.ts";
11
11
  import type { ModuleResolver } from "./ModuleResolver.ts";
12
12
  import { flatImports, parseSrcModule, type WeslAST } from "./ParseWESL.ts";
13
13
  import type {
@@ -21,6 +21,24 @@ import type {
21
21
  import { stdEnumerant, stdFn, stdType } from "./StandardTypes.ts";
22
22
  import { last } from "./Util.ts";
23
23
 
24
+ /** WGSL standard attributes whose params need binding (e.g., @workgroup_size). */
25
+ const wgslStandardAttributes = new Set([
26
+ "align",
27
+ "binding",
28
+ "blend_src",
29
+ "compute",
30
+ "const",
31
+ "fragment",
32
+ "group",
33
+ "id",
34
+ "invariant",
35
+ "location",
36
+ "must_use",
37
+ "size",
38
+ "vertex",
39
+ "workgroup_size",
40
+ ]);
41
+
24
42
  /**
25
43
  * BindIdents pass: link reference identifiers to declarations.
26
44
  *
@@ -281,6 +299,9 @@ function handleRef(
281
299
  // Skip binding for condition refs - they resolve via Conditions map (for now)
282
300
  if (ident.conditionRef) return;
283
301
 
302
+ // Skip binding for refs in non-WGSL attribute params (e.g., @test(description))
303
+ if (ident.attrParam && !wgslStandardAttributes.has(ident.attrParam)) return;
304
+
284
305
  const foundDecl =
285
306
  findDeclInModule(ident, liveDecls) ??
286
307
  findQualifiedImport(ident, bindContext);
@@ -385,7 +406,7 @@ function findExport(
385
406
  ): FoundDecl | undefined {
386
407
  const { resolver, conditions, virtuals } = ctx;
387
408
  const srcParts = srcModule.modulePath.split("::");
388
- const fqParts = resolveSuper(pathParts, srcParts);
409
+ const fqParts = resolveModulePath(pathParts, srcParts);
389
410
  const modulePath = fqParts.slice(0, -1).join("::");
390
411
 
391
412
  const moduleAst =
@@ -23,10 +23,12 @@ export interface ClickableErrorParams {
23
23
  error: Error;
24
24
  }
25
25
 
26
+ const isBrowser = "document" in globalThis;
27
+
26
28
  /** Throw an error with an embedded source map so that browser users can
27
29
  * click on the error in the browser debug console and see the wesl source code. */
28
30
  export function throwClickableError(params: ClickableErrorParams): void {
29
- if (!debug) throw params.error; // skip source map generation in prod
31
+ if (!debug || !isBrowser) throw params.error;
30
32
  const { url, text, lineNumber, lineColumn, length, error } = params;
31
33
 
32
34
  // Based on https://stackoverflow.com/questions/65274147/sourceurl-for-css
@@ -36,6 +36,26 @@ interface EmitContext {
36
36
  extracting: boolean;
37
37
  }
38
38
 
39
+ /** Valid WGSL standard attributes (from spec). Non-WGSL attributes are stripped.
40
+ * See: https://www.w3.org/TR/WGSL/#attributes
41
+ * Note: @builtin, @diagnostic, @interpolate are parsed as separate attribute types. */
42
+ const wgslStandardAttributes = new Set([
43
+ "align",
44
+ "binding",
45
+ "blend_src",
46
+ "compute",
47
+ "const",
48
+ "fragment",
49
+ "group",
50
+ "id",
51
+ "invariant",
52
+ "location",
53
+ "must_use",
54
+ "size",
55
+ "vertex",
56
+ "workgroup_size",
57
+ ]);
58
+
39
59
  /** Traverse the AST, starting from root elements, emitting WGSL for each. */
40
60
  export function lowerAndEmit(params: EmitParams): void {
41
61
  const { srcBuilder, rootElems, conditions } = params;
@@ -45,9 +65,7 @@ export function lowerAndEmit(params: EmitParams): void {
45
65
  const validElements = skipConditionalFiltering
46
66
  ? rootElems
47
67
  : filterValidElements(rootElems, conditions);
48
- validElements.forEach(e => {
49
- lowerAndEmitElem(e, emitContext);
50
- });
68
+ for (const e of validElements) lowerAndEmitElem(e, emitContext);
51
69
  }
52
70
 
53
71
  function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
@@ -303,9 +321,7 @@ function emitSynthetic(e: SyntheticElem, ctx: EmitContext): void {
303
321
 
304
322
  function emitContents(elem: ContainerElem, ctx: EmitContext): void {
305
323
  const validElements = filterValidElements(elem.contents, ctx.conditions);
306
- validElements.forEach(e => {
307
- lowerAndEmitElem(e, ctx);
308
- });
324
+ for (const e of validElements) lowerAndEmitElem(e, ctx);
309
325
  }
310
326
 
311
327
  /** Emit contents with leading/trailing whitespace trimming (V2 parser). */
@@ -444,6 +460,9 @@ function emitAttribute(e: AttributeElem, ctx: EmitContext): boolean {
444
460
  }
445
461
 
446
462
  if (kind === "@attribute") {
463
+ if (!wgslStandardAttributes.has(e.attribute.name)) {
464
+ return false; // non-WGSL attribute, dropped from output
465
+ }
447
466
  emitStandardAttribute(e, ctx);
448
467
  return true;
449
468
  }
@@ -503,30 +522,36 @@ export function diagnosticControlToString(
503
522
 
504
523
  export function expressionToString(elem: ExpressionElem): string {
505
524
  const { kind } = elem;
506
- if (kind === "binary-expression") {
507
- return `${expressionToString(elem.left)} ${elem.operator.value} ${expressionToString(elem.right)}`;
508
- } else if (kind === "unary-expression") {
509
- return `${elem.operator.value}${expressionToString(elem.expression)}`;
510
- } else if (kind === "ref") {
511
- return elem.ident.originalName;
512
- } else if (kind === "literal") {
513
- return elem.value;
514
- } else if (kind === "parenthesized-expression") {
515
- return `(${expressionToString(elem.expression)})`;
516
- } else if (kind === "component-expression") {
517
- return `${expressionToString(elem.base)}[${elem.access}]`;
518
- } else if (kind === "component-member-expression") {
519
- return `${expressionToString(elem.base)}.${elem.access}`;
520
- } else if (kind === "call-expression") {
521
- const fn = elem.function;
522
- const name =
523
- fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName;
524
- const targs = elem.templateArgs ? `<...>` : "";
525
- return `${name}${targs}(${elem.arguments.map(expressionToString).join(", ")})`;
526
- } else if (kind === "type") {
527
- return elem.name.originalName;
528
- } else {
529
- assertUnreachable(kind);
525
+ switch (kind) {
526
+ case "binary-expression": {
527
+ const left = expressionToString(elem.left);
528
+ const right = expressionToString(elem.right);
529
+ return `${left} ${elem.operator.value} ${right}`;
530
+ }
531
+ case "unary-expression":
532
+ return `${elem.operator.value}${expressionToString(elem.expression)}`;
533
+ case "ref":
534
+ return elem.ident.originalName;
535
+ case "literal":
536
+ return elem.value;
537
+ case "parenthesized-expression":
538
+ return `(${expressionToString(elem.expression)})`;
539
+ case "component-expression":
540
+ return `${expressionToString(elem.base)}[${elem.access}]`;
541
+ case "component-member-expression":
542
+ return `${expressionToString(elem.base)}.${elem.access}`;
543
+ case "call-expression": {
544
+ const fn = elem.function;
545
+ const name =
546
+ fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName;
547
+ const targs = elem.templateArgs ? `<...>` : "";
548
+ const args = elem.arguments.map(expressionToString).join(", ");
549
+ return `${name}${targs}(${args})`;
550
+ }
551
+ case "type":
552
+ return elem.name.originalName;
553
+ default:
554
+ assertUnreachable(kind);
530
555
  }
531
556
  }
532
557
 
@@ -2,11 +2,8 @@
2
2
 
3
3
  /**
4
4
  * Convert module path segments to relative file path.
5
- * Handles package/packageName prefixes and super:: resolution.
5
+ * Resolves package:: and super:: via srcModuleParts context.
6
6
  *
7
- * @param parts - module path as array e.g., ["package", "utils", "helper"]
8
- * @param packageName - the current package's name (required)
9
- * @param srcModuleParts - source module path for super:: resolution (optional)
10
7
  * @returns relative file path e.g., "utils/helper", or undefined if external
11
8
  */
12
9
  export function modulePartsToRelativePath(
@@ -14,7 +11,9 @@ export function modulePartsToRelativePath(
14
11
  packageName: string,
15
12
  srcModuleParts?: string[],
16
13
  ): string | undefined {
17
- const resolved = srcModuleParts ? resolveSuper(parts, srcModuleParts) : parts;
14
+ const resolved = srcModuleParts
15
+ ? resolveModulePath(parts, srcModuleParts)
16
+ : parts;
18
17
 
19
18
  const rootSegment = resolved[0];
20
19
  if (rootSegment === "package" || rootSegment === packageName) {
@@ -35,20 +34,23 @@ export function moduleToRelativePath(
35
34
  }
36
35
 
37
36
  /**
38
- * Resolve super:: elements to absolute module path.
39
- *
40
- * @param parts - module path with potential super:: elements
41
- * @param srcModuleParts - source module path for context
42
- * @returns absolute module path parts (no super:: elements)
37
+ * Resolve package:: and super:: to absolute module path.
38
+ * - package:: replaced with actual package name from source module
39
+ * - super:: climbs up the module path hierarchy
43
40
  */
44
- export function resolveSuper(
41
+ export function resolveModulePath(
45
42
  parts: string[],
46
43
  srcModuleParts: string[],
47
44
  ): string[] {
48
- const lastSuper = parts.lastIndexOf("super");
49
- if (lastSuper === -1) return parts;
45
+ // Handle package:: - replace with actual package name from source module
46
+ const resolved =
47
+ parts[0] === "package" ? [srcModuleParts[0], ...parts.slice(1)] : parts;
48
+
49
+ // Handle super:: - climb up the module path
50
+ const lastSuper = resolved.lastIndexOf("super");
51
+ if (lastSuper === -1) return resolved;
50
52
  const base = srcModuleParts.slice(0, -(lastSuper + 1));
51
- return [...base, ...parts.slice(lastSuper + 1)];
53
+ return [...base, ...resolved.slice(lastSuper + 1)];
52
54
  }
53
55
 
54
56
  /** Normalize debug root to end with / or be empty. */
package/src/Scope.ts CHANGED
@@ -41,6 +41,9 @@ export interface RefIdent extends IdentBase {
41
41
  /** True for identifiers in @if/@elif conditions. Binding skips these (for now). */
42
42
  conditionRef?: true;
43
43
 
44
+ /** Attribute name if this ref is inside an attribute param (for skip-binding check). */
45
+ attrParam?: string;
46
+
44
47
  // LATER consider tracking the current ast in BindIdents so that this field is unnecessary
45
48
  ast: WeslAST; // AST from module that contains this ident (to find imports during decl binding)
46
49
 
@@ -154,7 +154,9 @@ function parseStandardAttribute(ctx: ParsingContext): AttributeElem | null {
154
154
 
155
155
  let params: UnknownExpressionElem[] | undefined;
156
156
  if (stream.matchText("(")) {
157
+ ctx.parsingAttrParam = name;
157
158
  params = parseAttributeParams(ctx);
159
+ ctx.parsingAttrParam = undefined;
158
160
  expect(stream, ")", "attribute parameters");
159
161
  }
160
162
 
@@ -65,14 +65,19 @@ export class ParsingContext {
65
65
  return scope.parent === null;
66
66
  }
67
67
 
68
+ /** Attribute name being parsed (for marking refs in attr params). */
69
+ parsingAttrParam?: string;
70
+
68
71
  createRefIdent(name: string): RefIdent {
69
- return {
72
+ const ref: RefIdent = {
70
73
  kind: "ref",
71
74
  originalName: name,
72
75
  ast: this.state.stable,
73
76
  id: nextIdentId(),
74
77
  refIdentElem: null as any, // linked by caller
75
78
  };
79
+ if (this.parsingAttrParam) ref.attrParam = this.parsingAttrParam;
80
+ return ref;
76
81
  }
77
82
 
78
83
  createDeclIdent(name: string, isGlobal = false): DeclIdent {
@@ -20,6 +20,36 @@ test("@diagnostic attribute on statement", async () => {
20
20
  expectTokenMatch(result, src);
21
21
  });
22
22
 
23
+ test("non-WGSL attribute @test stripped from output", async () => {
24
+ const src = `
25
+ @test
26
+ fn myTest() { }
27
+ `;
28
+ const expected = `fn myTest() { }`;
29
+ const result = await linkTest(src);
30
+ expectTokenMatch(result, expected);
31
+ });
32
+
33
+ test("non-WGSL attribute @test with params stripped", async () => {
34
+ const src = `
35
+ @test(my_test_name)
36
+ fn myTest() { }
37
+ `;
38
+ const expected = `fn myTest() { }`;
39
+ const result = await linkTest(src);
40
+ expectTokenMatch(result, expected);
41
+ });
42
+
43
+ test("non-WGSL attribute @custom stripped from output", async () => {
44
+ const src = `
45
+ @custom
46
+ fn myFn() { }
47
+ `;
48
+ const expected = `fn myFn() { }`;
49
+ const result = await linkTest(src);
50
+ expectTokenMatch(result, expected);
51
+ });
52
+
23
53
  test("link an alias", async () => {
24
54
  const src = /* wgsl */ `
25
55
  alias Num = f32;