Skip to content

Source Generator Architecture

Roger Johansson edited this page Jan 14, 2026 · 1 revision

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.

Overview

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
Loading

Key Files

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

stdlib-compat.json

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"]
      }
    }
  }
}

Structure

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

Attribute-Based Registration

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) { ... }
}

Available Attributes

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

Automatic Stub Generation

The generator compares implemented members against stdlib-compat.json and automatically generates NotImplementedException stubs for any missing members.

How It Works

  1. Generator reads all [JsHostMethod] etc. attributes from the C# class
  2. Looks up the corresponding entry in stdlib-compat.json
  3. Computes the set difference (expected - implemented)
  4. Generates stub methods that throw NotImplementedException

Generated Stub Example

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, ... });

Why Stubs?

  1. Test262 compatibility: Tests expect methods to exist even if not implemented
  2. Clear error messages: Users see exactly which method is missing
  3. Specification coverage: Easy to track what still needs implementation
  4. Graceful degradation: Code can detect method existence before calling

Diagnostic Reporting

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.

Adding a New Method

To implement a missing method:

  1. Find the prototype class (e.g., ArrayPrototype.cs)
  2. Add a method with the appropriate attribute:
[JsHostMethod(Name = "toLocaleString")]
public JsValue ToLocaleString(JsValue receiver, JsValue[] args)
{
    var array = RequireInstance(receiver);
    // Implementation here...
}
  1. Rebuild - the generator will stop creating the stub automatically

Implementation Status Tracking

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" {} \;

Generated File Locations

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
        └── ...

Verifying Implementation Status

To accurately determine what's implemented:

  1. Check C# source: Look for [JsHostMethod] attributes
  2. Check generated code: Look for NotImplementedException in generated files
  3. Run tests: Test262 failures indicate missing implementations

Methods in C# with attributes = truly implemented
Methods only in generated stubs = not implemented (throws NotImplementedException)


See Also

Clone this wiki locally