-
Notifications
You must be signed in to change notification settings - Fork 1
Source Generator Architecture
The engine uses Roslyn Source Generators to automatically wire up JavaScript built-in types with minimal boilerplate. This page documents the PrototypeSourceGenerator system that powers the standard library.
flowchart LR
subgraph Input
A[C# Prototype Classes<br/>with Attributes]
B[stdlib-compat.json<br/>MDN Browser Compat Data]
end
subgraph Generator
C[PrototypeSourceGenerator]
end
subgraph Output
D[.Prototype.g.cs<br/>Base prototype setup]
E[.Prototype.Members.g.cs<br/>Method registrations]
F[NotImplementedException stubs<br/>for missing methods]
end
A --> C
B --> C
C --> D
C --> E
C --> F
| File | Purpose |
|---|---|
src/Asynkron.JsEngine.Generators/PrototypeSourceGenerator.cs |
Main source generator |
src/Asynkron.JsEngine.Generators/Data/stdlib-compat.json |
ECMAScript specification from MDN |
src/Asynkron.JsEngine/Runtime/StandardLibrary/*.cs |
Prototype implementations |
This JSON file is derived from MDN browser-compat-data and defines every method, getter, setter, and symbol member expected by the ECMAScript specification.
{
"source": {
"repository": "mdn/browser-compat-data",
"tag": "v7.2.3"
},
"builtins": {
"Array": {
"constructor": {
"methods": ["from", "fromAsync", "isArray", "of"],
"symbolGetters": ["species"]
},
"prototype": {
"methods": ["at", "concat", "copyWithin", ...],
"symbolMethods": ["iterator", "unscopables"]
}
}
}
}Each builtin has two sections:
-
constructor: Static methods and properties on the constructor (e.g.,
Array.from) -
prototype: Instance methods and properties (e.g.,
[].map)
Each section contains:
-
methods: Regular methods -
getters: Property getters -
setters: Property setters -
symbolMethods: Symbol-keyed methods (e.g.,[Symbol.iterator]) -
symbolGetters: Symbol-keyed getters (e.g.,[Symbol.species]) -
symbolSetters: Symbol-keyed setters
Prototype classes use attributes to declare JavaScript members:
[JsPrototype(
IntrinsicName = "Array",
ToStringTag = "Array",
InstanceType = typeof(JsArray))]
public sealed partial class ArrayPrototype
{
[JsHostMethod(Name = "push")]
public JsValue Push(JsValue receiver, JsValue[] args) { ... }
[JsHostGetter(Name = "length")]
public JsValue GetLength(JsValue receiver) { ... }
[JsHostSymbolMethod(SymbolName = "iterator")]
public JsValue Iterator(JsValue receiver, JsValue[] args) { ... }
}| Attribute | Purpose | Generated Property |
|---|---|---|
[JsPrototype] |
Marks class as a prototype | Wires up CreatePrototype() |
[JsConstructor] |
Marks class as a constructor | Wires up CreateConstructor() |
[JsHostMethod] |
Instance method | prototype.methodName |
[JsHostGetter] |
Instance getter |
prototype.propertyName (get) |
[JsHostSetter] |
Instance setter |
prototype.propertyName (set) |
[JsHostSymbolMethod] |
Symbol method | prototype[Symbol.xxx] |
[JsHostSymbolGetter] |
Symbol getter |
prototype[Symbol.xxx] (get) |
[JsMethodAlias] |
Method alias | Same method under different name |
The generator compares implemented members against stdlib-compat.json and automatically generates NotImplementedException stubs for any missing members.
- Generator reads all
[JsHostMethod]etc. attributes from the C# class - Looks up the corresponding entry in
stdlib-compat.json - Computes the set difference (expected - implemented)
- Generates stub methods that throw
NotImplementedException
For a missing Array.prototype.toLocaleString method:
// <auto-generated />
var missingMethod_toLocaleString = new HostFunction(
(thisValue, args) => throw new NotImplementedException(
"Array.prototype.toLocaleString is not yet implemented"),
realm, isConstructor: false);
missingMethod_toLocaleString.DefineProperty("length",
new PropertyDescriptor { Value = 0d, Writable = false, ... });
missingMethod_toLocaleString.DefineProperty("name",
new PropertyDescriptor { Value = "toLocaleString", ... });
prototype.DefineProperty("toLocaleString",
new PropertyDescriptor { Value = missingMethod_toLocaleString, ... });- Test262 compatibility: Tests expect methods to exist even if not implemented
- Clear error messages: Users see exactly which method is missing
- Specification coverage: Easy to track what still needs implementation
- Graceful degradation: Code can detect method existence before calling
When EmitDiagnostics is enabled, the generator reports missing members as compiler warnings:
warning JSE001: Array.prototype is missing: toLocaleString, toReversed
This helps track implementation progress during development.
To implement a missing method:
- Find the prototype class (e.g.,
ArrayPrototype.cs) - Add a method with the appropriate attribute:
[JsHostMethod(Name = "toLocaleString")]
public JsValue ToLocaleString(JsValue receiver, JsValue[] args)
{
var array = RequireInstance(receiver);
// Implementation here...
}- Rebuild - the generator will stop creating the stub automatically
To check what's implemented vs. stubbed:
# Build with diagnostics to see warnings
dotnet build -p:EmitJsCompat=true
# Or check generated files in obj/
find . -name "*.Prototype.Members.g.cs" -exec grep -l "NotImplementedException" {} \;After build, generated files appear in:
obj/Debug/net9.0/generated/
└── Asynkron.JsEngine.Generators/
└── Asynkron.JsEngine.Generators.PrototypeSourceGenerator/
├── ArrayPrototype.Prototype.g.cs
├── ArrayPrototype.Prototype.Members.g.cs
├── DatePrototype.Prototype.g.cs
└── ...
To accurately determine what's implemented:
-
Check C# source: Look for
[JsHostMethod]attributes -
Check generated code: Look for
NotImplementedExceptionin generated files - Run tests: Test262 failures indicate missing implementations
Methods in C# with attributes = truly implemented
Methods only in generated stubs = not implemented (throws NotImplementedException)
- Standard-Library-Architecture - Overview of the standard library design
- Standard-Library-Index - Complete implementation status reference