Header menu logo Fabulous.AST

Rewrite

Overview

Rewrite applies a transformation to every node of a given kind reachable from a Fantomas Oak — through type definitions, member bodies, patterns, types and attributes. It is handy for cross-cutting passes that aren't tied to where you wrote the builder: fold constants, migrate a deprecated API everywhere, wrap calls with instrumentation, convert a union to a record, and so on.

The functions are curried with the target last, so they drop into the usual pipeline. The WidgetBuilder<Oak> forms return the same builder when nothing changed (reference equality is preserved on unchanged subtrees).

Function

Signature

Rewrite.expr

(Expr -> Expr) -> WidgetBuilder<Oak> -> WidgetBuilder<Oak>

Rewrite.exprInOak

(Expr -> Expr) -> Oak -> Oak

Rewrite.typeDefn

(TypeDefn -> TypeDefn) -> WidgetBuilder<Oak> -> WidgetBuilder<Oak>

Rewrite.typeDefnInOak

(TypeDefn -> TypeDefn) -> Oak -> Oak

#r "../../src/Fabulous.AST/bin/Release/netstandard2.1/publish/Fantomas.Core.dll"
#r "../../src/Fabulous.AST/bin/Release/netstandard2.1/publish/Fabulous.AST.dll"
#r "../../src/Fabulous.AST/bin/Release/netstandard2.1/publish/Fantomas.FCS.dll"

open Fabulous.AST
open Fantomas.Core.SyntaxOak
open Fantomas.FCS.Text
open type Fabulous.AST.Ast

Rewriting expressions

Rewrite.expr applies your function to every Expr in the tree (bottom-up, children first). That makes it the right tool for cross-cutting changes that aren't tied to any single builder call — the kind of thing other ecosystems reach for AST passes to do. A couple of small helpers for building raw Expr nodes, used by the examples below:

let ident(name: string) : Expr =
    Expr.Constant(Constant.FromText(SingleTextNode(name, Range.Zero)))

let intExpr(value: int) : Expr =
    Expr.Constant(Constant.FromText(SingleTextNode(string value, Range.Zero)))

let (|IntLit|_|)(e: Expr) : int option =
    match e with
    | Expr.Constant(Constant.FromText n) ->
        match System.Int32.TryParse n.Text with
        | true, v -> Some v
        | _ -> None
    | _ -> None

Optimize: constant folding

Generators often emit naive, uniform expressions (x * 1 + 0) because that is the easiest thing to produce from data. A folding pass tidies them up afterwards — the same idea as an optimizing compiler's constant-folding pass (LLVM, GCC) or a JavaScript minifier. The generator stays simple; the cleanup is one reusable pass over the result. Because the walk is bottom-up, nested redundancy collapses in a single pass.

let foldConstants(e: Expr) : Expr =
    match e with
    | Expr.InfixApp n ->
        match n.Operator.Text, n.LeftHandSide, n.RightHandSide with
        | "+", lhs, IntLit 0
        | "+", IntLit 0, lhs
        | "*", lhs, IntLit 1
        | "*", IntLit 1, lhs -> lhs
        | "+", IntLit a, IntLit b -> intExpr(a + b)
        | "*", IntLit a, IntLit b -> intExpr(a * b)
        | _ -> e
    | _ -> e

Oak() {
    AnonymousModule() {
        Value("area", InfixAppExpr(InfixAppExpr(ConstantExpr(Constant "width"), "*", Int(1)), "+", Int(0)))

        Value("total", InfixAppExpr(Int(2), "+", Int(3)))
    }
}
|> Rewrite.expr foldConstants
|> Gen.mkOak
|> Gen.run
|> printfn "%s"

// produces the following code:
let area = width
let total = 5

Migrate an API (codemod)

Rewrite a non-idiomatic call into its better form wherever it appears — the same job a jscodeshift React codemod, a Scalafix rule, or go fix does. Here List.length xs = 0 (which is O(n)) becomes List.isEmpty xs (O(1)) across the whole tree:

let useIsEmpty(e: Expr) : Expr =
    match e with
    | Expr.InfixApp n when n.Operator.Text = "=" ->
        match n.LeftHandSide, n.RightHandSide with
        | Expr.App app, IntLit 0 ->
            match app.FunctionExpr with
            | Expr.Constant(Constant.FromText f) when f.Text = "List.length" ->
                Expr.App(ExprAppNode(ident "List.isEmpty", List.ofSeq app.Arguments, Range.Zero))
            | _ -> e
        | _ -> e
    | _ -> e

Oak() {
    AnonymousModule() {
        Value(
            "isEmpty",
            InfixAppExpr(AppExpr(ConstantExpr(Constant "List.length"), [ ConstantExpr(Constant "xs") ]), "=", Int(0))
        )
    }
}
|> Rewrite.expr useIsEmpty
|> Gen.mkOak
|> Gen.run
|> printfn "%s"

// produces the following code:
let isEmpty = List.isEmpty xs

Instrument calls

Weave a cross-cutting concern into generated code without touching every call site — the way OpenTelemetry's auto-instrumentation or an AspectJ aspect does. Here every compute call is wrapped with a timing helper. (No infinite loop: the walk is a single pass, so the newly-created withTiming node is not revisited.)

let instrument(e: Expr) : Expr =
    match e with
    | Expr.App app ->
        match app.FunctionExpr with
        | Expr.Constant(Constant.FromText f) when f.Text = "compute" ->
            let wrapped =
                Expr.Paren(
                    ExprParenNode(SingleTextNode("(", Range.Zero), e, SingleTextNode(")", Range.Zero), Range.Zero)
                )

            Expr.App(ExprAppNode(ident "withTiming", [ wrapped ], Range.Zero))
        | _ -> e
    | _ -> e

Oak() {
    AnonymousModule() { Value("result", AppExpr(ConstantExpr(Constant "compute"), [ ConstantExpr(Constant "input") ])) }
}
|> Rewrite.expr instrument
|> Gen.mkOak
|> Gen.run
|> printfn "%s"

// produces the following code:
let result = withTiming (compute input)

Rewriting type definitions

Rewrite.typeDefn visits each TypeDefn, so a pass can replace one shape with another. This converts a single-case union into the equivalent record by reusing the case's fields (a UnionCaseNode's Fields are already FieldNodes):

let unionToRecord(td: TypeDefn) : TypeDefn =
    match td with
    | TypeDefn.Union n when n.UnionCases.Length = 1 ->
        let itd = n :> ITypeDefn

        TypeDefn.Record(
            TypeDefnRecordNode(
                itd.TypeName,
                n.Accessibility,
                SingleTextNode("{", Range.Zero),
                n.UnionCases.Head.Fields,
                SingleTextNode("}", Range.Zero),
                itd.Members,
                Range.Zero
            )
        )
    | _ -> td

Oak() { AnonymousModule() { Union("Point") { UnionCase("Point", [ Field("X", Int()); Field("Y", Int()) ]) } } }
|> Rewrite.typeDefn unionToRecord
|> Gen.mkOak
|> Gen.run
|> printfn "%s"

// produces the following code:
type Point = { X: int; Y: int }

Working with raw Oak nodes

When you already hold a raw Oak (rather than a WidgetBuilder<Oak>), use the InOak variants. They take the same transformation and return a rewritten Oak:

let oak = Gen.mkOak widget
let optimized = oak |> Rewrite.exprInOak foldConstants
let asRecord = oak |> Rewrite.typeDefnInOak unionToRecord
namespace Fabulous
namespace Fabulous.AST
namespace Fantomas
namespace Fantomas.Core
module SyntaxOak from Fantomas.Core
namespace Fantomas.FCS
namespace Fantomas.FCS.Text
type Ast = class end
val ident: name: string -> Expr
val name: string
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
Multiple items
union case TextSegment.Expr: WidgetBuilder<FillExprNode> * int -> TextSegment

--------------------
module Expr from Fabulous.AST

--------------------
type Expr = | Lazy of ExprLazyNode | Single of ExprSingleNode | Constant of Constant | Null of SingleTextNode | Quote of ExprQuoteNode | Typed of ExprTypedNode | New of ExprNewNode | Tuple of ExprTupleNode | StructTuple of ExprStructTupleNode | ArrayOrList of ExprArrayOrListNode ... static member Node: x: Expr -> Node member HasParentheses: bool
union case Expr.Constant: Constant -> Expr
Multiple items
static member Ast.Constant: value: string -> WidgetBuilder<Constant>

--------------------
module Constant from Fabulous.AST

--------------------
type Constant = | FromText of SingleTextNode | Unit of UnitNode | Measure of ConstantMeasureNode static member Node: c: Constant -> NodeBase
union case Constant.FromText: SingleTextNode -> Constant
Multiple items
module SingleTextNode from Fabulous.AST

--------------------
type SingleTextNode = inherit NodeBase new: idText: string * range: range -> SingleTextNode override Children: Node array member Text: string

--------------------
new: idText: string * range: range -> SingleTextNode
Multiple items
module Range from Fantomas.FCS.Text

--------------------
[<Struct>] type Range = member End: pos member EndColumn: int member EndLine: int member EndRange: range member FileName: string member IsSynthetic: bool member Start: pos member StartColumn: int member StartLine: int member StartRange: range ...
property Range.Zero: range with get
val intExpr: value: int -> Expr
val value: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val e: Expr
type 'T option = Option<'T>
val n: SingleTextNode
namespace System
[<Struct>] type Int32 = member CompareTo: value: int -> int + 1 overload member Equals: obj: int -> bool + 1 overload member GetHashCode: unit -> int member GetTypeCode: unit -> TypeCode member ToString: unit -> string + 3 overloads member TryFormat: utf8Destination: Span<byte> * bytesWritten: byref<int> * ?format: ReadOnlySpan<char> * ?provider: IFormatProvider -> bool + 1 overload static member Abs: value: int -> int static member BigMul: left: int * right: int -> int64 static member Clamp: value: int * min: int * max: int -> int static member CopySign: value: int * sign: int -> int ...
<summary>Represents a 32-bit signed integer.</summary>
System.Int32.TryParse(s: string, result: byref<int>) : bool
System.Int32.TryParse(s: System.ReadOnlySpan<char>, result: byref<int>) : bool
System.Int32.TryParse(utf8Text: System.ReadOnlySpan<byte>, result: byref<int>) : bool
System.Int32.TryParse(s: string, provider: System.IFormatProvider, result: byref<int>) : bool
System.Int32.TryParse(s: System.ReadOnlySpan<char>, provider: System.IFormatProvider, result: byref<int>) : bool
System.Int32.TryParse(utf8Text: System.ReadOnlySpan<byte>, provider: System.IFormatProvider, result: byref<int>) : bool
System.Int32.TryParse(s: string, style: System.Globalization.NumberStyles, provider: System.IFormatProvider, result: byref<int>) : bool
System.Int32.TryParse(s: System.ReadOnlySpan<char>, style: System.Globalization.NumberStyles, provider: System.IFormatProvider, result: byref<int>) : bool
System.Int32.TryParse(utf8Text: System.ReadOnlySpan<byte>, style: System.Globalization.NumberStyles, provider: System.IFormatProvider, result: byref<int>) : bool
property SingleTextNode.Text: string with get
val v: int
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val foldConstants: e: Expr -> Expr
union case Expr.InfixApp: ExprInfixAppNode -> Expr
val n: ExprInfixAppNode
property ExprInfixAppNode.Operator: SingleTextNode with get
property ExprInfixAppNode.LeftHandSide: Expr with get
property ExprInfixAppNode.RightHandSide: Expr with get
val lhs: Expr
active recognizer IntLit: Expr -> int option
val a: int
val b: int
Multiple items
static member Ast.Oak: unit -> CollectionBuilder<Oak,'marker>

--------------------
module Oak from Fabulous.AST

--------------------
type Oak = inherit NodeBase new: parsedHashDirectives: ParsedHashDirectiveNode list * modulesOrNamespaces: ModuleOrNamespaceNode list * m: range -> Oak override Children: Node array member ModulesOrNamespaces: ModuleOrNamespaceNode list member ParsedHashDirectives: ParsedHashDirectiveNode list

--------------------
new: parsedHashDirectives: ParsedHashDirectiveNode list * modulesOrNamespaces: ModuleOrNamespaceNode list * m: range -> Oak
static member Ast.AnonymousModule: unit -> CollectionBuilder<ModuleOrNamespaceNode,ModuleDecl>
static member Ast.Value: name: string * value: string * returnType: string -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: string * returnType: WidgetBuilder<Type> -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: string -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: WidgetBuilder<Constant> * returnType: string -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: WidgetBuilder<Constant> * returnType: WidgetBuilder<Type> -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: WidgetBuilder<Constant> -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: WidgetBuilder<Expr> * returnType: string -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: WidgetBuilder<Expr> * returnType: WidgetBuilder<Type> -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: string * value: WidgetBuilder<Expr> -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.Value: name: WidgetBuilder<Constant> * value: WidgetBuilder<Constant> * returnType: string -> WidgetBuilder<BindingNode>
   (+0 other overloads)
static member Ast.InfixAppExpr: lhs: WidgetBuilder<Constant> * operator: string * rhs: string -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: WidgetBuilder<Expr> * operator: string * rhs: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: string * operator: string * rhs: string -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: string * operator: string * rhs: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: WidgetBuilder<Constant> * operator: string * rhs: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: string * operator: string * rhs: WidgetBuilder<Expr> -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: WidgetBuilder<Constant> * operator: string * rhs: WidgetBuilder<Expr> -> WidgetBuilder<Expr>
static member Ast.InfixAppExpr: lhs: WidgetBuilder<Expr> * operator: string * rhs: WidgetBuilder<Expr> -> WidgetBuilder<Expr>
static member Ast.ConstantExpr: value: string -> WidgetBuilder<Expr>
static member Ast.ConstantExpr: value: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
static member Ast.Int: value: int -> WidgetBuilder<Constant>
static member Ast.Int: unit -> WidgetBuilder<Type>
module Rewrite from Fabulous.AST
<summary> Bottom-up rewriter for expressions inside an Oak tree. Visits every <see cref="T:Fantomas.Core.SyntaxOak.Expr" /> reachable from the input (including those nested inside type definitions, member bodies, patterns, types and attributes) and applies the caller-supplied function after each node's children are rebuilt. Reference equality is preserved on unchanged subtrees, so a no-op rewrite returns the same Oak instance. Curried with the target last, so it drops into a pipeline: <c>widget |&gt; Rewrite.expr f |&gt; Gen.run</c>. </summary>
val expr: f: (Expr -> Expr) -> widget: WidgetBuilder<Oak> -> WidgetBuilder<Oak>
<summary> Rewrites every Expr in the Oak produced by <paramref name="widget" />, returning the same <c>WidgetBuilder</c> when nothing changed. </summary>
type Gen = static member mkOak: root: WidgetBuilder<'node> -> 'node static member parse: source: string -> string + 1 overload static member run: oak: Oak -> string + 1 overload
<summary> Renders a widget tree to F# source and verifies the result. <c>mkOak</c> turns the root widget into a Fantomas node (recursively building its children), <c>run</c> formats that node to source (optionally with a config), and <c>parse</c> round-trips the source back through the parser to confirm it is syntactically valid. Designed for pipeline use, e.g. <c>widget |&gt; Gen.parse</c> or <c>widget |&gt; Gen.mkOak |&gt; Gen.run |&gt; Gen.parse</c>. </summary>
static member Gen.mkOak: root: WidgetBuilder<'node> -> 'node
static member Gen.run: oak: Oak -> string
static member Gen.run: oak: Oak * config: Fantomas.Core.FormatConfig -> string
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
val useIsEmpty: e: Expr -> Expr
union case Expr.App: ExprAppNode -> Expr
val app: ExprAppNode
property ExprAppNode.FunctionExpr: Expr with get
val f: SingleTextNode
Multiple items
type ExprAppNode = inherit NodeBase new: functionExpr: Expr * arguments: Expr list * range: range -> ExprAppNode member Arguments: Expr list override Children: Node array member FunctionExpr: Expr

--------------------
new: functionExpr: Expr * arguments: Expr list * range: range -> ExprAppNode
Multiple items
static member Ast.List: unit -> WidgetBuilder<Type>

--------------------
module List from Fabulous.AST

--------------------
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val ofSeq: source: 'T seq -> 'T list
property ExprAppNode.Arguments: Expr list with get
static member Ast.AppExpr: name: string -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: WidgetBuilder<Expr> -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: string * item: string -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: WidgetBuilder<Constant> * item: string -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: WidgetBuilder<Expr> * item: string -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: string * item: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: WidgetBuilder<Constant> * item: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: WidgetBuilder<Expr> * item: WidgetBuilder<Constant> -> WidgetBuilder<Expr>
   (+0 other overloads)
static member Ast.AppExpr: name: string * item: WidgetBuilder<Expr> -> WidgetBuilder<Expr>
   (+0 other overloads)
val instrument: e: Expr -> Expr
val wrapped: Expr
union case Expr.Paren: ExprParenNode -> Expr
Multiple items
type ExprParenNode = inherit NodeBase new: openingParen: SingleTextNode * expr: Expr * closingParen: SingleTextNode * range: range -> ExprParenNode override Children: Node array member ClosingParen: SingleTextNode member Expr: Expr member OpeningParen: SingleTextNode

--------------------
new: openingParen: SingleTextNode * expr: Expr * closingParen: SingleTextNode * range: range -> ExprParenNode
val unionToRecord: td: TypeDefn -> TypeDefn
val td: TypeDefn
Multiple items
static member Ast.TypeDefn: name: string -> CollectionBuilder<TypeDefn,MemberDefn>
static member Ast.TypeDefn: name: string * constructor: WidgetBuilder<ImplicitConstructorNode> -> CollectionBuilder<TypeDefn,MemberDefn>
static member Ast.TypeDefn: name: string * constructor: WidgetBuilder<Pattern> -> CollectionBuilder<TypeDefn,MemberDefn>

--------------------
module TypeDefn from Fabulous.AST
<summary> Common attributes shared by all TypeDefn widgets </summary>

--------------------
type TypeDefn = | Enum of TypeDefnEnumNode | Union of TypeDefnUnionNode | Record of TypeDefnRecordNode | None of TypeNameNode | Abbrev of TypeDefnAbbrevNode | Explicit of TypeDefnExplicitNode | Augmentation of TypeDefnAugmentationNode | Delegate of TypeDefnDelegateNode | Regular of TypeDefnRegularNode static member Node: x: TypeDefn -> Node static member TypeDefnNode: x: TypeDefn -> ITypeDefn
union case TypeDefn.Union: TypeDefnUnionNode -> TypeDefn
val n: TypeDefnUnionNode
property TypeDefnUnionNode.UnionCases: UnionCaseNode list with get
property List.Length: int with get
val itd: ITypeDefn
type ITypeDefn = abstract Members: MemberDefn list abstract TypeName: TypeNameNode
union case TypeDefn.Record: TypeDefnRecordNode -> TypeDefn
Multiple items
type TypeDefnRecordNode = inherit NodeBase interface ITypeDefn new: typeNameNode: TypeNameNode * accessibility: SingleTextNode option * openingBrace: SingleTextNode * fields: FieldNode list * closingBrace: SingleTextNode * members: MemberDefn list * range: range -> TypeDefnRecordNode member Accessibility: SingleTextNode option override Children: Node array member ClosingBrace: SingleTextNode member Fields: FieldNode list member OpeningBrace: SingleTextNode

--------------------
new: typeNameNode: TypeNameNode * accessibility: SingleTextNode option * openingBrace: SingleTextNode * fields: FieldNode list * closingBrace: SingleTextNode * members: MemberDefn list * range: range -> TypeDefnRecordNode
property ITypeDefn.TypeName: TypeNameNode with get
property TypeDefnUnionNode.Accessibility: SingleTextNode option with get
property List.Head: UnionCaseNode with get
property UnionCaseNode.Fields: FieldNode list with get
property ITypeDefn.Members: MemberDefn list with get
Multiple items
static member Ast.Union: name: string -> CollectionBuilder<TypeDefn,UnionCaseNode>

--------------------
module Union from Fabulous.AST
Multiple items
static member Ast.UnionCase: name: string * fields: (string * WidgetBuilder<Type>) seq -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * fields: (string * string) seq -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * field: string -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * field: WidgetBuilder<FieldNode> -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * fields: string seq -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * field: WidgetBuilder<Type> -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * fields: WidgetBuilder<Type> seq -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string * fields: WidgetBuilder<FieldNode> seq -> WidgetBuilder<UnionCaseNode>
static member Ast.UnionCase: name: string -> WidgetBuilder<UnionCaseNode>

--------------------
module UnionCase from Fabulous.AST
Multiple items
static member Ast.Field: name: string * fieldType: string -> WidgetBuilder<FieldNode>
static member Ast.Field: name: string * fieldType: WidgetBuilder<Type> -> WidgetBuilder<FieldNode>
static member Ast.Field: fieldType: string -> WidgetBuilder<FieldNode>
static member Ast.Field: fieldType: WidgetBuilder<Type> -> WidgetBuilder<FieldNode>

--------------------
module Field from Fabulous.AST
val typeDefn: g: (TypeDefn -> TypeDefn) -> widget: WidgetBuilder<Oak> -> WidgetBuilder<Oak>
<summary> Rewrites every TypeDefn in the Oak produced by <paramref name="widget" />, returning the same <c>WidgetBuilder</c> when nothing changed. Unlike <see cref="M:Fabulous.AST.Rewrite.expr" /> this visits type definitions themselves, so a pass can replace one shape with another (e.g. a union with a record). </summary>
val oak: obj
val optimized: obj
val asRecord: obj

Type something to start searching.