Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4e8c86c
Add a compiler intrinsic for the 'string' operator
charlesroddie Jun 21, 2026
1d5769c
Generate a match-based ToString for unions under --reflectionfree
charlesroddie Jun 21, 2026
bfb3939
Extract mkStringConcat helper for arity-dispatched String.Concat
charlesroddie Jun 21, 2026
e5710fa
Fix generated union ToString for generic unions
charlesroddie Jun 21, 2026
d5712c8
Render union ToString fields like option (null -> "null")
charlesroddie Jun 21, 2026
26f0e53
Tidy reflection-free union ToString tests
charlesroddie Jun 21, 2026
87e6870
Add reflection-free ToString to Result and Choice
charlesroddie Jun 21, 2026
956a4b0
Generate a single-line ToString for records under --reflectionfree
charlesroddie Jun 21, 2026
a021166
Update FSharp.Core surface-area baselines for Result/Choice ToString
charlesroddie Jun 21, 2026
8eef294
Add release notes
charlesroddie Jun 21, 2026
3754def
Generate a single-line ToString for anonymous records under --reflect…
charlesroddie Jun 21, 2026
a5f33ca
Test that a hand-written ToString override is kept under --reflection…
charlesroddie Jun 22, 2026
7ed0d8d
Rename ToString generators for clarity
charlesroddie Jun 22, 2026
dd3d75c
Restore tabular layout for string_operator_info in TcGlobals
charlesroddie Jun 22, 2026
1a5065e
Add reflection-free ToString tests for field shapes, structs, anon re…
charlesroddie Jun 23, 2026
2c108ed
Add EmittedIL tests for reflection-free record and union ToString
charlesroddie Jun 23, 2026
6a2199c
Generate reflection-free ToString in the augmentation phase
charlesroddie Jun 23, 2026
474b9f9
Guard generated reflection-free ToString against deep-recursion overflow
charlesroddie Jun 27, 2026
e130e50
Test the reflection-free ToString deep-recursion guard
charlesroddie Jun 27, 2026
6e276b1
revert ToString additions to fsharp.core types
charlesroddie Jun 29, 2026
e3c1a34
Remove stale FSharp.Core release note for the reverted Result/Choice …
charlesroddie Jun 29, 2026
93ee8ce
Merge remote-tracking branch 'upstream/main' into DUsRecordsCompiledT…
charlesroddie Jun 29, 2026
dcb4fc3
Merge branch 'main' into DUsRecordsCompiledToStrings
charlesroddie Jul 1, 2026
c4debaf
Merge branch 'main' into DUsRecordsCompiledToStrings
charlesroddie Jul 1, 2026
817e9de
Fix code formatting in IlxGen.fs (dotnet fantomas)
charlesroddie Jul 2, 2026
770601f
Merge branch 'main' into DUsRecordsCompiledToStrings
charlesroddie Jul 2, 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 docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
* Debug: rework for expressions stepping ([PR #19894](https://github.com/dotnet/fsharp/pull/19894))
* Debug: rework conditional erasure, fix stepping over literals ([PR #19897](https://github.com/dotnet/fsharp/pull/19897))
* Debug: fix if and match condition sequence points ([PR #19932](https://github.com/dotnet/fsharp/pull/19932))
* Under `--reflectionfree`, discriminated unions and records now get a generated `ToString` (rendering each field like `Option` does) instead of falling back to the namespace-qualified type name. ([PR #19976](https://github.com/dotnet/fsharp/pull/19976))

@T-Gro T-Gro Jul 3, 2026

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.

The --reflectionfree rendering diverges from %+A in enough ways to be worth documenting/announcing as acceptance wording. Not release-note material — suggest a durable docs/reflectionfree-printing.md this note can link to. Also currently omits anonymous records.

F# %+A (default) --reflectionfree
{ X = 1; Y = 2 } { X = 1 Y = 2 } { X = 1; Y = 2 }
B 5 B 5 B(5)
C (3, "hi") C (3, "hi") C(3, hi)
Wrap "hi" Wrap "hi" Wrap(hi)
char field 'a' a
{ P = None } None null
{ A = [|1;2;3|] } [|1; 2; 3|] System.Int32[]
{ V = ValueSome 2 } ValueSome 2 2
U () U () U(null)
5.0 5.0 5
250uy / 42n / 1.5M 250uy / 42n / 1.5M 250 / 42 / 1.5
[<StructuredFormatDisplay("Custom<{X}>")>] Custom<5> { X = 5 } (ignored)
``My Case`` 5 My Case 5 ``My Case``(5) (see AugmentWithHashCompare comment)

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.

It could go to https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting#a-formatting to communicate the differences.

I also want the table in this PR so that people can come in and express how acceptable the differences are (vs building on top of this PR and replicating reflection-based %A in codegen by switching over field type).

(pinging people who interacted with this PR: @kerams , @ShalokShalom , @smoothdeveloper , @jwosty )

@kerams kerams Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wrap(hi)

I think quotes should be preserved around strings so that there is no ambiguity without consulting the type's shape.

System.Int32[]

If we had something like tostring levels, then we could choose how complex the emission should be (should ToString generate code to iterate over a portion of any collection?). Then again, with --reflectionfree people supposedly care more about AOT than these strings, and one can always write a custom ToString, so maybe adding levels to this is needless complexity.


### Changed

Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Core/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
### Changed

* Added complexity documentation (Big-O notation) to all 462 functions across Array, List, Seq, Map, and Set collection modules. ([PR #19240](https://github.com/dotnet/fsharp/pull/19240))
* `Result` and `Choice` now have a reflection-free `ToString` consistent with `Option`'s `Some(x)` style (e.g. `Ok 0` renders as `"Ok(0)"` instead of `"Ok 0"`). ([PR #19976](https://github.com/dotnet/fsharp/pull/19976))

### Breaking Changes
128 changes: 118 additions & 10 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2222,7 +2222,6 @@ type AnonTypeGenerationTable() =

mkLdfldMethodDef ("get_" + propName, ILMemberAccess.Public, false, ilTy, fldName, fldTy, ILAttributes.Empty, attrs)
|> g.AddMethodGeneratedAttributes
yield! genToStringMethod ilTy
]

let ilBaseTy = (if isStruct then g.iltyp_ValueType else g.ilg.typ_Object)
Expand Down Expand Up @@ -2325,6 +2324,10 @@ type AnonTypeGenerationTable() =
Some(mkLocalValRef augmentation.EqualsExactWithComparer)
)

// Generate ToString through the synthetic record tycon (renders "{| Name = value; ... |}" under
// --reflectionfree, otherwise sprintf "%+A"). Done here, not in ilMethods above, because it needs the tycon.
let ilToStringMethodDefs = genToStringMethod (ilTy, tycon)

// Build the ILTypeDef. We don't rely on the normal record generation process because we want very specific field names

let ilTypeDefAttribs =
Expand All @@ -2347,7 +2350,7 @@ type AnonTypeGenerationTable() =
ilGenericParams,
ilBaseTy,
ilInterfaceTys,
mkILMethods (ilCtorDef :: ilMethods),
mkILMethods (ilCtorDef :: ilMethods @ ilToStringMethodDefs),
ilFieldDefs,
emptyILTypeDefs,
ilProperties,
Expand Down Expand Up @@ -3803,7 +3806,7 @@ and GenAllocRecd cenv cgbuf eenv ctorInfo (tcref, argTys, args, m) sequel =

and GenAllocAnonRecd cenv cgbuf eenv (anonInfo: AnonRecdTypeInfo, tyargs, args, m) sequel =
let anonCtor, _anonMethods, anonType =
cgbuf.mgbuf.LookupAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)
cgbuf.mgbuf.LookupAnonType((fun (ilThisTy, tycon) -> GenRecordToStringMethod(cenv, cgbuf.mgbuf, EnvForTycon tycon eenv, ilThisTy, mkLocalTyconRef tycon, m, "{| ", " |}")), anonInfo)

let boxity = anonType.Boxity
GenExprs cenv cgbuf eenv args
Expand All @@ -3817,7 +3820,7 @@ and GenAllocAnonRecd cenv cgbuf eenv (anonInfo: AnonRecdTypeInfo, tyargs, args,

and GenGetAnonRecdField cenv cgbuf eenv (anonInfo: AnonRecdTypeInfo, e, tyargs, n, m) sequel =
let _anonCtor, anonMethods, anonType =
cgbuf.mgbuf.LookupAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)
cgbuf.mgbuf.LookupAnonType((fun (ilThisTy, tycon) -> GenRecordToStringMethod(cenv, cgbuf.mgbuf, EnvForTycon tycon eenv, ilThisTy, mkLocalTyconRef tycon, m, "{| ", " |}")), anonInfo)

let boxity = anonType.Boxity
let ilTypeArgs = GenTypeArgs cenv m eenv.tyenv tyargs
Expand Down Expand Up @@ -10842,7 +10845,7 @@ and GenImplFile cenv (mgbuf: AssemblyBuilder) mainInfoOpt eenv (implFile: Checke

// Generate all the anonymous record types mentioned anywhere in this module
for anonInfo in anonRecdTypes.Values do
mgbuf.GenerateAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)
mgbuf.GenerateAnonType((fun (ilThisTy, tycon) -> GenRecordToStringMethod(cenv, mgbuf, EnvForTycon tycon eenv, ilThisTy, mkLocalTyconRef tycon, m, "{| ", " |}")), anonInfo)
Comment thread
T-Gro marked this conversation as resolved.
Outdated

let withQName (loc: CompileLocation) =
{ loc with
Expand Down Expand Up @@ -11210,9 +11213,6 @@ and GenAbstractBinding cenv eenv tref (vref: ValRef) =
else
[], [], []

and GenToStringMethod cenv eenv ilThisTy m =
GenPrintingMethod cenv eenv "ToString" ilThisTy m

/// Generate a ToString/get_Message method that calls 'sprintf "%A"'
and GenPrintingMethod cenv eenv methName ilThisTy m =
let g = cenv.g
Expand Down Expand Up @@ -11279,6 +11279,114 @@ and GenPrintingMethod cenv eenv methName ilThisTy m =
| _ -> ()
]

/// Generate the 'ToString' method for a union type. Normally this calls 'sprintf "%+A"' (see
/// GenPrintingMethod). Under reflection-free code generation 'sprintf' is unavailable, so instead emit a
/// match over the cases that builds "CaseName(f0, f1, ...)" using the 'string' operator on each field.
/// Format one field value the same way option/list do (LanguagePrimitives.anyToStringShowingNull):
/// render null as "null", otherwise via the 'string' operator.
and GenFieldToString (cenv: cenv, m: range, fe: Expr) =
let g = cenv.g
let fieldTy = tyOfExpr g fe
let v, ve = mkCompGenLocal m "field" fieldTy
mkCompGenLet m v fe (mkNonNullCond g m g.string_ty (mkCallBox g m fieldTy ve) (mkCallStringOperator g m fieldTy ve) (mkString g m "null"))

/// Emit a [<CompilerGenerated>] virtual ToString override whose body is the given string-typed expression.
/// 'thisv' is the 'this' value (stored at arg 0) referenced by bodyExpr.
and GenToStringMethodFromExpr (cenv: cenv, mgbuf: AssemblyBuilder, eenv: IlxGenEnv, thisv: Val, bodyExpr: Expr) =
let g = cenv.g
let eenvForMeth = AddStorageForLocalVals g [ (thisv, Arg 0) ] eenv
let ilMethodBody = CodeGenMethodForExpr cenv mgbuf ([], "ToString", eenvForMeth, 0, Some thisv, bodyExpr, Return)

let mdef =
mkILNonGenericVirtualInstanceMethod (
"ToString",
Comment thread
T-Gro marked this conversation as resolved.
ILMemberAccess.Public,
[],
mkILReturn g.ilg.typ_String,
MethodBody.IL(InterruptibleLazy.FromValue ilMethodBody)
)

[ mdef.With(customAttrs = mkILCustomAttrs [ g.CompilerGeneratedAttribute ]) ]

/// Build the 'this' local for a generated ToString (a byref for struct types) and the type instantiation.
and GenToStringThis (cenv: cenv, tcref: TyconRef, m: range) =
let g = cenv.g
let tinst, ty = generalizeTyconRef g tcref
let thisv, thise = mkCompGenLocal m "this" (if isStructTy g ty then mkByrefTy g ty else ty)
tinst, thisv, thise

and GenUnionToStringMethod (cenv: cenv, mgbuf: AssemblyBuilder, eenv: IlxGenEnv, ilThisTy: ILType, tcref: TyconRef, m: range) =
let g = cenv.g

if not g.useReflectionFreeCodeGen then
GenPrintingMethod cenv eenv "ToString" ilThisTy m
else
let tinst, thisv, thise = GenToStringThis (cenv, tcref, m)
Comment thread
T-Gro marked this conversation as resolved.
Outdated

let mbuilder = MatchBuilder(DebugPointAtBinding.NoneAtInvisible, m)

let mkResult (ucase: UnionCase) =
let cref = tcref.MakeNestedUnionCaseRef ucase
let rfields = ucase.RecdFields

if isNil rfields then
mkString g m ucase.DisplayName
else
// provene is an expression proven to be of this case (the value itself for struct unions,
// otherwise a 'UnionCaseProof'), from which fields can be read.
let mkBody (provene: Expr) =
let fieldStrs =
rfields
|> List.mapi (fun j _ -> GenFieldToString (cenv, m, mkUnionCaseFieldGetProvenViaExprAddr (provene, cref, tinst, j, m)))

let sep = mkString g m ", "

let fieldsWithSeps =
fieldStrs |> List.mapi (fun i fe -> if i = 0 then [ fe ] else [ sep; fe ]) |> List.concat

let parts = mkString g m (ucase.DisplayName + "(") :: fieldsWithSeps @ [ mkString g m ")" ]
mkStringConcat (g, m, parts)

if cref.Tycon.IsStructOrEnumTycon then
mkBody thise
else
let ucv, ucve = mkCompGenLocal m "thisCast" (mkProvenUnionCaseTy cref tinst)
mkCompGenLet m ucv (mkUnionCaseProof (thise, cref, tinst, m)) (mkBody ucve)

let cases =
tcref.UnionCasesAsList
|> List.map (fun ucase ->
let cref = tcref.MakeNestedUnionCaseRef ucase
mkCase (DecisionTreeTest.UnionCase(cref, tinst), mbuilder.AddResultTarget(mkResult ucase)))

let dtree = TDSwitch(thise, cases, None, m)
let matchExpr = mbuilder.Close(dtree, m, g.string_ty)

GenToStringMethodFromExpr (cenv, mgbuf, eenv, thisv, matchExpr)

/// Generate a record's ToString as a single line "{ F1 = v1; F2 = v2 }" (no line breaks, unlike "%+A"),
/// fields formatted like union fields. openBrace/closeBrace are "{ "/" }" for records and "{| "/" |}" for
/// anonymous records. Under non-reflection-free codegen, falls back to sprintf "%+A".
and GenRecordToStringMethod (cenv: cenv, mgbuf: AssemblyBuilder, eenv: IlxGenEnv, ilThisTy: ILType, tcref: TyconRef, m: range, openBrace: string, closeBrace: string) =
let g = cenv.g

if not g.useReflectionFreeCodeGen then
GenPrintingMethod cenv eenv "ToString" ilThisTy m
else
let tinst, thisv, thise = GenToStringThis (cenv, tcref, m)

let fieldParts =
tcref.AllInstanceFieldsAsList
|> List.mapi (fun i fspec ->
let fref = tcref.MakeNestedRecdFieldRef fspec
let value = GenFieldToString (cenv, m, mkRecdFieldGetViaExprAddr (thise, fref, tinst, m))
let nameEq = mkString g m (fspec.DisplayName + " = ")
if i = 0 then [ nameEq; value ] else [ mkString g m "; "; nameEq; value ])
|> List.concat

let parts = mkString g m openBrace :: fieldParts @ [ mkString g m closeBrace ]
GenToStringMethodFromExpr (cenv, mgbuf, eenv, thisv, mkStringConcat (g, m, parts))

and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option =
let g = cenv.g
let tcref = mkLocalTyconRef tycon
Expand Down Expand Up @@ -11863,7 +11971,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
yield mkILSimpleStorageCtor (Some g.ilg.typ_Object.TypeSpec, ilThisTy, [], [], reprAccess, None, eenv.imports)

if not (tycon.HasMember g "ToString" []) then
yield! GenToStringMethod cenv eenv ilThisTy m
yield! GenRecordToStringMethod(cenv, mgbuf, eenvinner, ilThisTy, tcref, m, "{ ", " }")

| TFSharpTyconRepr r when tycon.IsFSharpDelegateTycon ->

Expand All @@ -11887,7 +11995,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
| _ -> ()

| TFSharpTyconRepr { fsobjmodel_kind = TFSharpUnion } when not (tycon.HasMember g "ToString" []) ->
yield! GenToStringMethod cenv eenv ilThisTy m
yield! GenUnionToStringMethod(cenv, mgbuf, eenvinner, ilThisTy, tcref, m)
| _ -> ()
]

Expand Down
14 changes: 1 addition & 13 deletions src/Compiler/Optimize/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2524,19 +2524,7 @@ and MakeOptimizedSystemStringConcatCall cenv env m args =

let args = optimizeArgs args []

let expr =
match args with
| [ arg ] ->
arg
| [ arg1; arg2 ] ->
mkStaticCall_String_Concat2 g m arg1 arg2
| [ arg1; arg2; arg3 ] ->
mkStaticCall_String_Concat3 g m arg1 arg2 arg3
| [ arg1; arg2; arg3; arg4 ] ->
mkStaticCall_String_Concat4 g m arg1 arg2 arg3 arg4
| args ->
let arg = mkArray (g.string_ty, args, m)
mkStaticCall_String_Concat_Array g m arg
let expr = mkStringConcat (g, m, args)

match expr with
| Expr.Op(TOp.ILCall(_, _, _, _, _, _, _, ilMethRef, _, _, _) as op, tyargs, args, m)
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ type TcGlobals(

let v_byte_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "byte" , None , Some "ToByte", [vara], ([[varaTy]], v_byte_ty))
let v_sbyte_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "sbyte" , None , Some "ToSByte", [vara], ([[varaTy]], v_sbyte_ty))
let v_string_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "string", None, Some "ToString", [vara], ([[varaTy]], v_string_ty))
Comment thread
T-Gro marked this conversation as resolved.
Outdated
let v_int16_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "int16" , None , Some "ToInt16", [vara], ([[varaTy]], v_int16_ty))
let v_uint16_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "uint16" , None , Some "ToUInt16", [vara], ([[varaTy]], v_uint16_ty))
let v_int32_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "int32" , None , Some "ToInt32", [vara], ([[varaTy]], v_int32_ty))
Expand Down Expand Up @@ -1594,6 +1595,7 @@ type TcGlobals(

member _.byte_operator_info = v_byte_operator_info
member _.sbyte_operator_info = v_sbyte_operator_info
member _.string_operator_info = v_string_operator_info
member _.int16_operator_info = v_int16_operator_info
member _.uint16_operator_info = v_uint16_operator_info
member _.int32_operator_info = v_int32_operator_info
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TcGlobals.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,8 @@ type internal TcGlobals =

member sbyte_operator_info: IntrinsicValRef

member string_operator_info: IntrinsicValRef

member sbyte_tcr: TypedTree.EntityRef

member sbyte_ty: TypedTree.TType
Expand Down
14 changes: 14 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,9 @@ module internal Makers =
let mkCallNewFormat (g: TcGlobals) m aty bty cty dty ety formatStringExpr =
mkApps g (typedExprForIntrinsic g m g.new_format_info, [ [ aty; bty; cty; dty; ety ] ], [ formatStringExpr ], m)

let mkCallStringOperator (g: TcGlobals) m argTy e =
mkApps g (typedExprForIntrinsic g m g.string_operator_info, [ [ argTy ] ], [ e ], m)

let tryMkCallBuiltInWitness (g: TcGlobals) traitInfo argExprs m =
let info, tinst = g.MakeBuiltInWitnessInfo traitInfo
let vref = ValRefForIntrinsic info
Expand Down Expand Up @@ -1570,6 +1573,17 @@ module internal Makers =
m
)

/// Concatenate string-valued expressions, choosing the cheapest String.Concat overload by arity.
/// An empty list yields "" and a singleton yields itself.
let mkStringConcat (g: TcGlobals, m: range, exprs: Expr list) =
match exprs with
| [] -> mkString g m ""
| [ arg ] -> arg
| [ arg1; arg2 ] -> mkStaticCall_String_Concat2 g m arg1 arg2
| [ arg1; arg2; arg3 ] -> mkStaticCall_String_Concat3 g m arg1 arg2 arg3
| [ arg1; arg2; arg3; arg4 ] -> mkStaticCall_String_Concat4 g m arg1 arg2 arg3 arg4
| _ -> mkStaticCall_String_Concat_Array g m (mkArray (g.string_ty, exprs, m))

// Quotations can't contain any IL.
// As a result, we aim to get rid of all IL generation in the typechecker and pattern match
// compiler, or else train the quotation generator to understand the generated IL.
Expand Down
7 changes: 7 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ module internal Makers =
val mkCallNewFormat:
TcGlobals -> range -> TType -> TType -> TType -> TType -> TType -> formatStringExpr: Expr -> Expr

/// Build a call to the 'string' operator (Operators.ToString) at the given argument type.
val mkCallStringOperator: TcGlobals -> range -> argTy: TType -> Expr -> Expr

val mkCallGetGenericComparer: TcGlobals -> range -> Expr

val mkCallGetGenericEREqualityComparer: TcGlobals -> range -> Expr
Expand Down Expand Up @@ -446,6 +449,10 @@ module internal Makers =

val mkStaticCall_String_Concat_Array: TcGlobals -> range -> Expr -> Expr

/// Concatenate string-valued expressions, choosing the cheapest String.Concat overload by arity.
/// An empty list yields "" and a singleton yields itself.
val mkStringConcat: TcGlobals * range * Expr list -> Expr

val mkDecr: TcGlobals -> range -> Expr -> Expr

val mkIncr: TcGlobals -> range -> Expr -> Expr
Expand Down
Loading
Loading