Skip to content
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
15564e9
Parse and preserve parameter decorators
jtenner Mar 27, 2026
75d7c6a
Reject surviving parameter decorators after transforms
jtenner Mar 27, 2026
65527dd
Cover transform-time removal of parameter decorators
jtenner Mar 27, 2026
32f69b2
Clarify transform-only parameter decorator contract
jtenner Mar 27, 2026
21f724f
Document transform hook timing for parameter decorators
jtenner Mar 27, 2026
c474861
Fix lint for parameter decorator fixtures
jtenner Mar 27, 2026
ec618c9
chore: ignore AS parameter decorator fixture in eslint
jtenner Apr 5, 2026
4505b5c
Fix parameter decorator review feedback
jtenner Apr 8, 2026
c267d6e
chore: remove legacy eslint config
jtenner Apr 11, 2026
6f75b5e
refactor: consolidate parameter decorator validation
jtenner Apr 13, 2026
53dc57c
chore: remove unused AST imports
jtenner Apr 13, 2026
bcbce9a
style: move parameter range to the end
jtenner Apr 13, 2026
ba37601
style: include explicit this decorators in function types
jtenner Apr 13, 2026
b1fa8aa
refactor: reuse decorator serialization for parameters
jtenner Apr 13, 2026
2a7f4fb
refactor: rename tryParseParameterDecorators helper
jtenner Apr 14, 2026
e401a30
Refactor parameter decorator parsing
jtenner Apr 15, 2026
d1ce4c7
Update NOTICE
PaperPrototype Apr 15, 2026
2ca7c6a
Update NOTICE
PaperPrototype Apr 15, 2026
93f7b31
undo unecessary comment changes
PaperPrototype Apr 15, 2026
9757397
Undo changes to index-wasm.ts
PaperPrototype Apr 16, 2026
2177f77
removed currentSourceStatementDepth and currentSourceStatementHasPara…
PaperPrototype Apr 16, 2026
9b2dc22
Remove NodeWalker and ParameterDecoratorValidator and validateParamet…
PaperPrototype Apr 16, 2026
9f9f3bd
Remove NodeWalker from ast.ts
PaperPrototype Apr 16, 2026
6743001
remove validateParameterDecorators from compiler.ts
PaperPrototype Apr 16, 2026
efe656b
Update compiler.ts
PaperPrototype Apr 16, 2026
3e8b163
Update compiler.ts
PaperPrototype Apr 16, 2026
516b2ec
Some claude stuff
PaperPrototype May 1, 2026
6615b02
undo tab in compiler.ts
PaperPrototype May 1, 2026
0e68e6a
Create temporary TODO.md for myself to write things down
PaperPrototype May 1, 2026
4afd421
Update TODO.md
PaperPrototype May 1, 2026
056ef1f
Transformer hook notes in TODO.md
PaperPrototype May 1, 2026
bc7f01e
Remove validation from compiler
PaperPrototype May 1, 2026
bcaacc6
Write test transformer in typescript to import types from assemblyscr…
PaperPrototype May 1, 2026
4deaa00
Update TODO.md
PaperPrototype May 1, 2026
ca13f28
Cleanup
PaperPrototype May 4, 2026
a98ec09
everything should be working now
PaperPrototype May 4, 2026
89290ae
fix `npm run bootstrap:debug`
PaperPrototype May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ under the licensing terms detailed in LICENSE:
* Mopsgamer <79159094+Mopsgamer@users.noreply.github.com>
* EDM115 <github@edm115.dev>
* Weixie Cui <cuiweixie@gmail.com>
* Abdiel Lopez <a.paperprototype@gmail.com>

Portions of this software are derived from third-party works licensed under
the following terms:
Expand Down
4 changes: 4 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export default defineConfig([

// FIXME: Tagged template literal tests with invalid escapes
"tests/compiler/templateliteral.ts",

// Decorators on `this` are not allowed typically in TypeScript, but this
// fixture exercises that AS-only syntax and is validated by transform tests.
"tests/transform/parameter-decorators.ts",
]),

js.configs.recommended,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@
"test:browser": "node --enable-source-maps tests/browser",
"test:asconfig": "cd tests/asconfig && npm run test",
"test:transform": "npm run test:transform:esm && npm run test:transform:cjs",
"test:transform:esm": "node bin/asc tests/compiler/empty --transform ./tests/transform/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/simple.js --noEmit",
"test:transform:cjs": "node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/simple.js --noEmit",
"test:transform:esm": "node bin/asc tests/compiler/empty --transform ./tests/transform/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/simple.js --noEmit && node bin/asc tests/transform/parameter-decorators.ts --transform ./tests/transform/remove-parameter-decorators.js --noEmit",
"test:transform:cjs": "node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/index.js --noEmit && node bin/asc tests/compiler/empty --transform ./tests/transform/cjs/simple.js --noEmit && node bin/asc tests/transform/parameter-decorators.ts --transform ./tests/transform/cjs/remove-parameter-decorators.js --noEmit",
"test:cli": "node tests/cli/options.js",
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
"asbuild:debug": "node bin/asc --config src/asconfig.json --target debug",
Expand Down
13 changes: 10 additions & 3 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,11 @@ export abstract class Node {
parameters: ParameterNode[],
returnType: TypeNode,
explicitThisType: NamedTypeNode | null,
explicitThisDecorators: DecoratorNode[] | null,
isNullable: bool,
range: Range
): FunctionTypeNode {
return new FunctionTypeNode(parameters, returnType, explicitThisType, isNullable, range);
return new FunctionTypeNode(parameters, returnType, explicitThisType, explicitThisDecorators, isNullable, range);
}

static createOmittedType(
Expand All @@ -181,9 +182,10 @@ export abstract class Node {
name: IdentifierExpression,
type: TypeNode,
initializer: Expression | null,
decorators: DecoratorNode[] | null,
range: Range
): ParameterNode {
return new ParameterNode(parameterKind, name, type, initializer, range);
return new ParameterNode(parameterKind, name, type, initializer, decorators, range);
}

// special
Expand Down Expand Up @@ -919,6 +921,8 @@ export class FunctionTypeNode extends TypeNode {
public returnType: TypeNode,
/** Explicitly provided this type, if any. */
public explicitThisType: NamedTypeNode | null, // can't be a function
/** Decorators on an explicit `this` parameter, if any. Preserved as transform-only syntax. */
public explicitThisDecorators: DecoratorNode[] | null,
/** Whether nullable or not. */
isNullable: bool,
/** Source range. */
Expand Down Expand Up @@ -965,12 +969,13 @@ export class ParameterNode extends Node {
public type: TypeNode,
/** Initializer expression, if any. */
public initializer: Expression | null,
/** Decorators, if any. Preserved as transform-only syntax so transforms can rewrite or remove them before validation. */
public decorators: DecoratorNode[] | null,
/** Source range. */
range: Range
) {
super(NodeKind.Parameter, range);
}

/** Implicit field declaration, if applicable. */
implicitFieldDeclaration: FieldDeclaration | null = null;
/** Common flags indicating specific traits. */
Expand Down Expand Up @@ -1664,6 +1669,8 @@ export class Source extends Node {
debugInfoIndex: i32 = -1;
/** Re-exported sources. */
exportPaths: string[] | null = null;
/** Function types with parameter or this-parameter decorators, revisited after transforms for validation. */
decoratedFunctionTypes: FunctionTypeNode[] | null = null;

/** Checks if this source represents native code. */
get isNative(): bool {
Expand Down
35 changes: 33 additions & 2 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@ export class Compiler extends DiagnosticEmitter {

// initialize lookup maps, built-ins, imports, exports, etc.
this.program.initialize();


this.validateParameterDecorators();
// Binaryen treats all function references as being leaked to the outside world when
// the module isn't marked as closed-world (see WebAssembly/binaryen#7135). Therefore,
// we should mark the module as closed-world when we're definitely sure it is.
Expand Down Expand Up @@ -10626,6 +10626,37 @@ export class Compiler extends DiagnosticEmitter {
this.currentType = toType;
return expr;
}

/** Rejects any parameter decorators not consumed by transforms. Runs once, after transforms have had their chance. */
private validateParameterDecorators(): void {
let program = this.program;
if (program.parameterDecoratorsValidated) return;
program.parameterDecoratorsValidated = true;
for (let source of program.sources) {
let fts = source.decoratedFunctionTypes;
if (!fts) continue;
for (let i = 0, k = fts.length; i < k; ++i) {
let ft = fts[i];
let thisDecorators = ft.explicitThisDecorators;
if (thisDecorators && thisDecorators.length > 0) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(thisDecorators[0].range, thisDecorators[thisDecorators.length - 1].range)
);
}
let params = ft.parameters;
for (let j = 0, m = params.length; j < m; ++j) {
let decorators = params[j].decorators;
if (decorators && decorators.length > 0) {
this.error(
DiagnosticCode.Decorators_are_not_valid_here,
Range.join(decorators[0].range, decorators[decorators.length - 1].range)
);
}
}
}
}
}
Copy link
Copy Markdown
Member

@MaxGraey MaxGraey May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t like that transformer validation happens in the compiler. If it’s not valid for upstream AS semantics, it’s better to move it to transform utils or add it to their pipeline automatically.

TS will not merge decorators soon, and even if it does, parameters are still unclear:
https://github.com/tc39/proposal-decorators/blob/master/EXTENSIONS.md#parameter-decorators-and-annotations

If param decorator semantics are not standardized there either, it feels wrong to validate this inside our compiler.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I’d just pass the bare minimum of what needs to be fed into the transformer, and let the transformer itself parse, canon and validate the decorators via the AST. Especially considering that this feature is pretty niche in terms of general use.

Copy link
Copy Markdown
Author

@PaperPrototype PaperPrototype May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I have no idea what I am doing 😅

Good to know about the decorators proposal for TS, I wasn't aware of it before 👍

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "add it to their pipeline automatically"?

Copy link
Copy Markdown
Member

@MaxGraey MaxGraey May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean extend transform's API by adding new validation entry point here between listFiles and afterParse and add validation hook to this entry point instead validate by itself. This would allow users to validate the code in the transformer themselves before compilation but after parsing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I’d just pass the bare minimum of what needs to be fed into the transformer, and let the transformer itself parse, canon and validate the decorators via the AST. Especially considering that this feature is pretty niche in terms of general use.

Sorry, I needed to reload the page before I saw this. When you say "Transformer" what are you referring to? I'm assuming you mean the implementors who will want to make use of these decorators? Eg. the example transformers in /tests/transform? Still very new to all the verbiage being thrown around here, sorry about the extreme noobiness

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh unless you are referring to the AssemblyScript AST transformer...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to reload the page before I saw this. When you say "Transformer" what are you referring to? I'm assuming you mean the implementors who will want to make use of these decorators?

yes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or don't do this and just use afterParse hook only.

Simply put, in this case we have ambiguity. Should we fail because we can’t handle such decorators, or not fail and hope that the Transformer will take on this role? An additional validator interface's method for Transformer API would answer this question clearly.

I think I finally get what you meant. I made some notes:

  • AST can be extended without breaking changes, this is perfectly fine and anything that is added will just be ignored by the compiler but at least transformer plugins can then make use of it. No need to validate and throw errors unless it is an AST parse error.
    • Need to add a transformer hook so transformers can add their own validation. This way AssemblyScript can delegate validation to transformers instead of having to handle it internally, which doesn't make sense because AS shouldn't be in charge of worring about that in the first place.

Is this correct? Just a "👍" is fine for me, unless I am completely wrong 🤣

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's pretty accurate

}

// helpers
Expand Down
20 changes: 17 additions & 3 deletions src/extra/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ export class ASTBuilder {
sb.push(isNullable ? "((" : "(");
let explicitThisType = node.explicitThisType;
if (explicitThisType) {
this.serializeParameterDecorators(node.explicitThisDecorators);
sb.push("this: ");
this.visitTypeNode(explicitThisType);
}
Expand Down Expand Up @@ -1153,6 +1154,7 @@ export class ASTBuilder {
let numParameters = parameters.length;
let explicitThisType = signature.explicitThisType;
if (explicitThisType) {
this.serializeParameterDecorators(signature.explicitThisDecorators);
sb.push("this: ");
this.visitTypeNode(explicitThisType);
}
Expand Down Expand Up @@ -1541,7 +1543,7 @@ export class ASTBuilder {

// other

serializeDecorator(node: DecoratorNode): void {
serializeDecorator(node: DecoratorNode, isInline: bool = false): void {
let sb = this.sb;
sb.push("@");
this.visitNode(node.name);
Expand All @@ -1556,16 +1558,28 @@ export class ASTBuilder {
this.visitNode(args[i]);
}
}
sb.push(")\n");
sb.push(")");
}
if (isInline) {
sb.push(" ");
} else {
sb.push("\n");
indent(sb, this.indentLevel);
}
}

serializeParameterDecorators(decorators: DecoratorNode[] | null): void {
if (decorators) {
for (let i = 0, k = decorators.length; i < k; ++i) {
this.serializeDecorator(decorators[i], true);
}
}
indent(sb, this.indentLevel);
}

serializeParameter(node: ParameterNode): void {
let sb = this.sb;
let kind = node.parameterKind;
this.serializeParameterDecorators(node.decorators);
let implicitFieldDeclaration = node.implicitFieldDeclaration;
if (implicitFieldDeclaration) {
this.serializeAccessModifiers(implicitFieldDeclaration);
Expand Down
Loading