-
Notifications
You must be signed in to change notification settings - Fork 865
Compiled ToStrings under -reflectionfree for DUs and Records #19976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4e8c86c
1d5769c
bfb3939
e5710fa
d5712c8
26f0e53
87e6870
956a4b0
a021166
8eef294
3754def
a5f33ca
7ed0d8d
dd3d75c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
|
@@ -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 = | ||
|
|
@@ -2347,7 +2350,7 @@ type AnonTypeGenerationTable() = | |
| ilGenericParams, | ||
| ilBaseTy, | ||
| ilInterfaceTys, | ||
| mkILMethods (ilCtorDef :: ilMethods), | ||
| mkILMethods (ilCtorDef :: ilMethods @ ilToStringMethodDefs), | ||
| ilFieldDefs, | ||
| emptyILTypeDefs, | ||
| ilProperties, | ||
|
|
@@ -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 | ||
|
|
@@ -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 | ||
|
|
@@ -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) | ||
|
|
||
| let withQName (loc: CompileLocation) = | ||
| { loc with | ||
|
|
@@ -11210,11 +11213,8 @@ 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 = | ||
| and GenSprintfPrintingMethod cenv eenv methName ilThisTy m = | ||
| let g = cenv.g | ||
|
|
||
| [ | ||
|
|
@@ -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 | ||
| /// GenSprintfPrintingMethod). 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 EmitToStringMethodDef (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", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if there was a string override present, written by hand? [<NoComparison;NoEquality>]
type MyDU = A of int
with override x.ToString() = "A"
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works, and a test is added in a5f33ca |
||
| 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 | ||
| GenSprintfPrintingMethod cenv eenv "ToString" ilThisTy m | ||
| else | ||
| let tinst, thisv, thise = GenToStringThis (cenv, tcref, m) | ||
|
Comment on lines
+11321
to
+11324
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that IlxGen has two different generators for ToString, I would appreciate a more distinct naming.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed in 7ed0d8d :
|
||
|
|
||
| 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) | ||
|
|
||
| EmitToStringMethodDef (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 | ||
| GenSprintfPrintingMethod 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 ] | ||
| EmitToStringMethodDef (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 | ||
|
|
@@ -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 -> | ||
|
|
||
|
|
@@ -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) | ||
| | _ -> () | ||
| ] | ||
|
|
||
|
|
@@ -12511,7 +12619,7 @@ and GenExnDef cenv mgbuf eenv m (exnc: Tycon) : ILTypeRef option = | |
| && not (exnc.HasMember g "Message" []) | ||
| && not (fspecs |> List.exists (fun rf -> rf.DisplayNameCore = "Message")) | ||
| then | ||
| yield! GenPrintingMethod cenv eenv "get_Message" ilThisTy m | ||
| yield! GenSprintfPrintingMethod cenv eenv "get_Message" ilThisTy m | ||
| ] | ||
|
|
||
| let interfaces = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make sure to cover anons and struct anons in testing.