Skip to content

Commit eeb5b48

Browse files
committed
C# fields in SRTP support
1 parent 6609928 commit eeb5b48

28 files changed

Lines changed: 926 additions & 63 deletions

docs/release-notes/.Language/preview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Warn (FS3884) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
44
* Added `MethodOverloadsCache` language feature (preview) that caches overload resolution results for repeated method calls, significantly improving compilation performance. ([PR #19072](https://github.com/dotnet/fsharp/pull/19072))
5+
* Support for .NET IL fields in SRTP member constraints (preview). Inline functions using SRTP `(^T: (member FieldName: FieldType) x)` now resolve against .NET class/struct fields, not just properties and methods. ([Language suggestion #1323](https://github.com/fsharp/fslang-suggestions/issues/1323))
56

67
### Fixed
78

release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ These release notes track our current efforts to document changes to the F# proj
1818

1919
### FSharp Compiler Service (main)
2020

21+
* Support for .NET IL fields in SRTP member constraints (preview feature)
2122
* In FSharpParsingOptions, rename ConditionalCompilationDefines --> ConditionalDefines
2223
* Some syntax tree nodes have changed, e.g. introduction of SyntaxTree trivia
2324
* Resolved expressions (FSharpExpr) now reveal debug points, you must match them explicitly using `DebugPoint(dp, expr)`

src/Compiler/Checking/ConstraintSolver.fs

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ type TraitConstraintSolution =
442442
| TTraitSolved of minfo: MethInfo * minst: TypeInst * staticTyOpt: TType option
443443
| TTraitSolvedRecdProp of fieldInfo: RecdFieldInfo * isSetProp: bool
444444
| TTraitSolvedAnonRecdProp of anonRecdTypeInfo: AnonRecdTypeInfo * typeInst: TypeInst * index: int
445+
| TTraitSolvedField of ty: TType * fieldInfo: ILFieldInfo * isSetField: bool
445446

446447
let BakedInTraitConstraintNames =
447448
[ "op_Division" ; "op_Multiply"; "op_Addition"
@@ -2006,9 +2007,51 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload
20062007
else
20072008
None
20082009

2010+
let fieldSearch =
2011+
if g.langVersion.SupportsFeature LanguageFeature.SupportILFieldsInSRTP then
2012+
let isGet = nm.StartsWithOrdinal("get_")
2013+
let isSet = nm.StartsWithOrdinal("set_")
2014+
2015+
let isValidGetter = isGet && argTys.IsEmpty
2016+
let isValidSetter = isSet && (argTys.Length = 1)
2017+
2018+
if not isRigid && (isValidGetter || isValidSetter) then
2019+
let fieldNm = nm[4..]
2020+
2021+
let fields =
2022+
[| for ty in supportTys do
2023+
let item =
2024+
TryFindIntrinsicNamedItemOfType
2025+
csenv.InfoReader
2026+
(fieldNm, AccessibleFromEverywhere, false)
2027+
FindMemberFlag.IgnoreOverrides
2028+
m
2029+
ty
2030+
2031+
match item with
2032+
| Some(ILFieldItem [ ilfinfo ]) when
2033+
ilfinfo.IsStatic = (not memFlags.IsInstance)
2034+
&& (isGet || not ilfinfo.IsInitOnly)
2035+
&& IsILFieldInfoAccessible g amap m AccessibleFromEverywhere ilfinfo
2036+
&& ilfinfo.LiteralValue.IsNone
2037+
&& not ilfinfo.IsSpecialName
2038+
->
2039+
yield (ilfinfo, isSet)
2040+
| _ -> () |]
2041+
2042+
match fields with
2043+
| [| (ilfinfo, isSet) |] ->
2044+
let ty = ilfinfo.FieldType(amap, m)
2045+
Some(ty, ilfinfo, isSet)
2046+
| _ -> None
2047+
else
2048+
None
2049+
else
2050+
None
2051+
20092052
// Now check if there are no feasible solutions at all
2010-
match minfos, recdPropSearch, anonRecdPropSearch with
2011-
| [], None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo ->
2053+
match minfos, recdPropSearch, anonRecdPropSearch, fieldSearch with
2054+
| [], None, None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo ->
20122055
if supportTys |> List.exists (isFunTy g) then
20132056
return! ErrorD (ConstraintSolverError(FSComp.SR.csExpectTypeWithOperatorButGivenFunction(ConvertValLogicalNameToDisplayNameCore nm), m, m2))
20142057
elif supportTys |> List.exists (isAnyTupleTy g) then
@@ -2069,36 +2112,69 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload
20692112
(fun (a, _) -> Option.isSome a)
20702113
(fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy)))
20712114

2072-
match anonRecdPropSearch, recdPropSearch, methOverloadResult with
2073-
| Some (anonInfo, tinst, i), None, None ->
2074-
// OK, the constraint is solved by a record property. Assert that the return types match.
2115+
match anonRecdPropSearch, recdPropSearch, fieldSearch, methOverloadResult with
2116+
| _, _, _, Some(calledMeth: CalledMeth<_>) ->
2117+
// Method/property has highest priority — wins even if a field also matched.
2118+
let minfo = calledMeth.Method
2119+
2120+
do! errors
2121+
let isInstance = minfo.IsInstance
2122+
2123+
if isInstance <> memFlags.IsInstance then
2124+
return!
2125+
if isInstance then
2126+
ErrorD(
2127+
ConstraintSolverError(
2128+
FSComp.SR.csMethodFoundButIsNotStatic (
2129+
(NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType),
2130+
(ConvertValLogicalNameToDisplayNameCore nm),
2131+
nm
2132+
),
2133+
m,
2134+
m2
2135+
)
2136+
)
2137+
else
2138+
ErrorD(
2139+
ConstraintSolverError(
2140+
FSComp.SR.csMethodFoundButIsStatic (
2141+
(NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType),
2142+
(ConvertValLogicalNameToDisplayNameCore nm),
2143+
nm
2144+
),
2145+
m,
2146+
m2
2147+
)
2148+
)
2149+
else
2150+
do! CheckMethInfoAttributes g m None minfo
2151+
return TTraitSolved(minfo, calledMeth.CalledTyArgs, calledMeth.OptionalStaticType)
2152+
2153+
| Some(anonInfo, tinst, i), _, _, None ->
2154+
// Anonymous record property — wins over record properties and fields.
20752155
let rty2 = List.item i tinst
20762156
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2
20772157
return TTraitSolvedAnonRecdProp(anonInfo, tinst, i)
20782158

2079-
| None, Some (rfinfo, isSetProp), None ->
2080-
// OK, the constraint is solved by a record property. Assert that the return types match.
2159+
| None, Some(rfinfo, isSetProp), _, None ->
2160+
// Record property — wins over fields.
20812161
let rty2 = if isSetProp then g.unit_ty else rfinfo.FieldType
20822162
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2
20832163
return TTraitSolvedRecdProp(rfinfo, isSetProp)
20842164

2085-
| None, None, Some (calledMeth: CalledMeth<_>) ->
2086-
// OK, the constraint is solved.
2087-
let minfo = calledMeth.Method
2165+
| None, None, Some(ty, ilfinfo, isSet), None ->
2166+
// IL field — lowest priority, only when nothing else matched.
2167+
let rty2 = if isSet then g.unit_ty else ty
2168+
do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2
20882169

2089-
do! errors
2090-
let isInstance = minfo.IsInstance
2091-
if isInstance <> memFlags.IsInstance then
2092-
return!
2093-
if isInstance then
2094-
ErrorD(ConstraintSolverError(FSComp.SR.csMethodFoundButIsNotStatic((NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), (ConvertValLogicalNameToDisplayNameCore nm), nm), m, m2 ))
2095-
else
2096-
ErrorD(ConstraintSolverError(FSComp.SR.csMethodFoundButIsStatic((NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), (ConvertValLogicalNameToDisplayNameCore nm), nm), m, m2 ))
2097-
else
2098-
do! CheckMethInfoAttributes g m None minfo
2099-
return TTraitSolved (minfo, calledMeth.CalledTyArgs, calledMeth.OptionalStaticType)
2170+
if isSet then
2171+
match argTys with
2172+
| [ argTy ] -> do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace argTy ty
2173+
| _ -> ()
21002174

2101-
| _ ->
2175+
return TTraitSolvedField(ty, ilfinfo, isSet)
2176+
2177+
| _ ->
21022178
do! AddUnsolvedMemberConstraint csenv ndeep m2 trace permitWeakResolution ignoreUnresolvedOverload traitInfo errors
21032179
return TTraitUnsolved
21042180
}
@@ -2167,6 +2243,11 @@ and RecordMemberConstraintSolution css m trace traitInfo traitConstraintSln =
21672243
TransactMemberConstraintSolution traitInfo trace sln
21682244
ResultD true
21692245

2246+
| TTraitSolvedField (ty, ilfinfo, isSet) ->
2247+
let sln = ILFieldSln(ty, ilfinfo.TypeInst, ilfinfo.ILFieldRef, ilfinfo.IsStatic, isSet)
2248+
TransactMemberConstraintSolution traitInfo trace sln
2249+
ResultD true
2250+
21702251
/// Convert a MethInfo into the data we save in the TAST
21712252
and MemberConstraintSolutionOfMethInfo css m minfo minst staticTyOpt =
21722253
#if !NO_TYPEPROVIDERS

src/Compiler/Checking/MethodCalls.fs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,7 +2164,7 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs =
21642164

21652165
let sln =
21662166
match traitInfo.Solution with
2167-
| None -> Choice5Of5()
2167+
| None -> Choice6Of6()
21682168
| Some sln ->
21692169

21702170
// Given the solution information, reconstruct the MethInfo for the solution
@@ -2179,25 +2179,54 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs =
21792179
| Some ilActualTypeRef ->
21802180
let actualTyconRef = ImportILTypeRef amap m ilActualTypeRef
21812181
MethInfo.CreateILExtensionMeth(amap, m, origTy, actualTyconRef, None, mdef)
2182-
Choice1Of5 (ilMethInfo, minst, staticTyOpt)
2182+
Choice1Of6 (ilMethInfo, minst, staticTyOpt)
21832183

21842184
| FSMethSln(ty, vref, minst, staticTyOpt) ->
2185-
Choice1Of5 (FSMeth(g, ty, vref, None), minst, staticTyOpt)
2185+
Choice1Of6 (FSMeth(g, ty, vref, None), minst, staticTyOpt)
21862186

21872187
| FSRecdFieldSln(tinst, rfref, isSetProp) ->
2188-
Choice2Of5 (tinst, rfref, isSetProp)
2188+
Choice2Of6 (tinst, rfref, isSetProp)
21892189

21902190
| FSAnonRecdFieldSln(anonInfo, tinst, i) ->
2191-
Choice3Of5 (anonInfo, tinst, i)
2191+
Choice3Of6 (anonInfo, tinst, i)
21922192

21932193
| ClosedExprSln expr ->
2194-
Choice4Of5 expr
2194+
Choice4Of6 expr
2195+
2196+
| ILFieldSln(ty, tinst, ilfref, isStatic, isSet) ->
2197+
Choice5Of6 (ty, tinst, ilfref, isStatic, isSet)
21952198

21962199
| BuiltInSln ->
2197-
Choice5Of5 ()
2200+
Choice6Of6 ()
21982201

21992202
match sln with
2200-
| Choice1Of5(minfo, methArgTys, staticTyOpt) ->
2203+
| Choice5Of6(ty, tinst, ilfref, isStatic, isSet) ->
2204+
let declaringTyconRef = ImportILTypeRef amap m ilfref.DeclaringTypeRef
2205+
let isStruct = isStructTy g (generalizedTyconRef g declaringTyconRef)
2206+
let boxity = if isStruct then ILBoxity.AsValue else ILBoxity.AsObject
2207+
let fspec = mkILFieldSpec (ilfref, mkILNamedTy boxity ilfref.DeclaringTypeRef [])
2208+
2209+
match isStatic, isSet with
2210+
| false, false ->
2211+
// Instance getter: ldfld
2212+
Some(Expr.Op(TOp.ILAsm([ mkNormalLdfld fspec ], [ ty ]), tinst, argExprs, m))
2213+
| false, true ->
2214+
// Instance setter: stfld (handle struct address-taking)
2215+
if isStruct && not (isByrefTy g (tyOfExpr g argExprs[0])) then
2216+
let wrap, h', _readonly, _writeonly =
2217+
mkExprAddrOfExpr g true false DefinitelyMutates argExprs[0] None m
2218+
2219+
Some(wrap (Expr.Op(TOp.ILAsm([ mkNormalStfld fspec ], []), tinst, [ h'; argExprs[1] ], m)))
2220+
else
2221+
Some(Expr.Op(TOp.ILAsm([ mkNormalStfld fspec ], []), tinst, argExprs, m))
2222+
| true, false ->
2223+
// Static getter: ldsfld
2224+
Some(Expr.Op(TOp.ILAsm([ mkNormalLdsfld fspec ], [ ty ]), tinst, argExprs, m))
2225+
| true, true ->
2226+
// Static setter: stsfld
2227+
Some(Expr.Op(TOp.ILAsm([ mkNormalStsfld fspec ], []), tinst, argExprs, m))
2228+
2229+
| Choice1Of6(minfo, methArgTys, staticTyOpt) ->
22012230
let argExprs =
22022231
// FIX for #421894 - typechecker assumes that coercion can be applied for the trait
22032232
// calls arguments but codegen doesn't emit coercion operations
@@ -2241,7 +2270,7 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs =
22412270
else
22422271
Some (MakeMethInfoCall amap m minfo methArgTys argExprs staticTyOpt)
22432272

2244-
| Choice2Of5 (tinst, rfref, isSet) ->
2273+
| Choice2Of6 (tinst, rfref, isSet) ->
22452274
match isSet, rfref.RecdField.IsStatic, argExprs.Length with
22462275
// static setter
22472276
| true, true, 1 ->
@@ -2271,17 +2300,17 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs =
22712300

22722301
| _ -> None
22732302

2274-
| Choice3Of5 (anonInfo, tinst, i) ->
2303+
| Choice3Of6 (anonInfo, tinst, i) ->
22752304
let tupInfo = anonInfo.TupInfo
22762305
if evalTupInfoIsStruct tupInfo && isByrefTy g (tyOfExpr g argExprs[0]) then
22772306
Some (mkAnonRecdFieldGetViaExprAddr (anonInfo, argExprs[0], tinst, i, m))
22782307
else
22792308
Some (mkAnonRecdFieldGet g (anonInfo, argExprs[0], tinst, i, m))
22802309

2281-
| Choice4Of5 expr ->
2310+
| Choice4Of6 expr ->
22822311
Some (MakeApplicationAndBetaReduce g (expr, tyOfExpr g expr, [], argExprs, m))
22832312

2284-
| Choice5Of5 () ->
2313+
| Choice6Of6 () ->
22852314
match traitInfo.Solution with
22862315
| None -> None // the trait has been generalized
22872316
| Some _->

src/Compiler/FSComp.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,6 +1809,7 @@ featureWarnWhenFunctionValueUsedAsInterpolatedStringArg,"Warn when a function va
18091809
featureMethodOverloadsCache,"Support for caching method overload resolution results for improved compilation performance."
18101810
featureImplicitDIMCoverage,"Implicit dispatch slot coverage for default interface member implementations"
18111811
featurePreprocessorElif,"#elif preprocessor directive"
1812+
featureSupportILFieldsInSRTP,"Support for .NET fields in SRTP member constraints"
18121813
3880,optsLangVersionOutOfSupport,"Language version '%s' is out of support. The last .NET SDK supporting it is available at https://dotnet.microsoft.com/en-us/download/dotnet/%s"
18131814
3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'."
18141815
3882,lexHashElifMustBeFirst,"#elif directive must appear as the first non-whitespace character on a line"

src/Compiler/Facilities/LanguageFeatures.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ type LanguageFeature =
108108
| MethodOverloadsCache
109109
| ImplicitDIMCoverage
110110
| PreprocessorElif
111+
| SupportILFieldsInSRTP
111112

112113
/// LanguageVersion management
113114
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
@@ -258,6 +259,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
258259
// F# preview (still preview in 10.0)
259260
LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work
260261
LanguageFeature.MethodOverloadsCache, previewVersion // Performance optimization for overload resolution
262+
LanguageFeature.SupportILFieldsInSRTP, previewVersion
261263
LanguageFeature.ImplicitDIMCoverage, languageVersion110
262264
]
263265

@@ -453,6 +455,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
453455
| LanguageFeature.MethodOverloadsCache -> FSComp.SR.featureMethodOverloadsCache ()
454456
| LanguageFeature.ImplicitDIMCoverage -> FSComp.SR.featureImplicitDIMCoverage ()
455457
| LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif ()
458+
| LanguageFeature.SupportILFieldsInSRTP -> FSComp.SR.featureSupportILFieldsInSRTP ()
456459

457460
/// Get a version string associated with the given feature.
458461
static member GetFeatureVersionString feature =

src/Compiler/Facilities/LanguageFeatures.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ type LanguageFeature =
9999
| MethodOverloadsCache
100100
| ImplicitDIMCoverage
101101
| PreprocessorElif
102+
| SupportILFieldsInSRTP
102103

103104
/// LanguageVersion management
104105
type LanguageVersion =

src/Compiler/Symbols/SymbolHelpers.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ module internal SymbolHelpers =
605605
| Some (TraitConstraintSln.FSRecdFieldSln _)
606606
| Some (TraitConstraintSln.FSAnonRecdFieldSln _)
607607
| Some (TraitConstraintSln.ClosedExprSln _)
608+
| Some (TraitConstraintSln.ILFieldSln _)
608609
| Some TraitConstraintSln.BuiltInSln
609610
| None ->
610611
GetXmlCommentForItemAux None infoReader m item

0 commit comments

Comments
 (0)