From 65fffab4938a158e86cdc909f0c7402ccf662510 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 9 Mar 2026 16:28:19 +0100 Subject: [PATCH] C# fields in SRTP support --- docs/release-notes/.Language/preview.md | 1 + src/Compiler/Checking/ConstraintSolver.fs | 122 +++- src/Compiler/Checking/MethodCalls.fs | 53 +- src/Compiler/FSComp.txt | 1 + src/Compiler/FSStrings.resx | 3 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/Symbols/SymbolHelpers.fs | 1 + src/Compiler/TypedTree/TypedTree.fs | 10 + src/Compiler/TypedTree/TypedTree.fsi | 10 + src/Compiler/TypedTree/TypedTreeOps.fs | 4 + src/Compiler/TypedTree/TypedTreePickle.fs | 6 + src/Compiler/xlf/FSComp.txt.cs.xlf | 9 +- src/Compiler/xlf/FSComp.txt.de.xlf | 9 +- src/Compiler/xlf/FSComp.txt.es.xlf | 9 +- src/Compiler/xlf/FSComp.txt.fr.xlf | 9 +- src/Compiler/xlf/FSComp.txt.it.xlf | 9 +- src/Compiler/xlf/FSComp.txt.ja.xlf | 9 +- src/Compiler/xlf/FSComp.txt.ko.xlf | 9 +- src/Compiler/xlf/FSComp.txt.pl.xlf | 9 +- src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 9 +- src/Compiler/xlf/FSComp.txt.ru.xlf | 9 +- src/Compiler/xlf/FSComp.txt.tr.xlf | 9 +- src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 9 +- src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 9 +- src/Compiler/xlf/FSStrings.cs.xlf | 5 + src/Compiler/xlf/FSStrings.de.xlf | 5 + src/Compiler/xlf/FSStrings.es.xlf | 5 + src/Compiler/xlf/FSStrings.fr.xlf | 5 + src/Compiler/xlf/FSStrings.it.xlf | 5 + src/Compiler/xlf/FSStrings.ja.xlf | 5 + src/Compiler/xlf/FSStrings.ko.xlf | 5 + src/Compiler/xlf/FSStrings.pl.xlf | 5 + src/Compiler/xlf/FSStrings.pt-BR.xlf | 5 + src/Compiler/xlf/FSStrings.ru.xlf | 5 + src/Compiler/xlf/FSStrings.tr.xlf | 5 + src/Compiler/xlf/FSStrings.zh-Hans.xlf | 5 + src/Compiler/xlf/FSStrings.zh-Hant.xlf | 5 + .../ConstraintSolver/MemberConstraints.fs | 540 ++++++++++++++++++ .../CompilerCompatApp/Program.fs | 25 +- .../CompilerCompatLib/Library.fs | 20 +- 41 files changed, 920 insertions(+), 62 deletions(-) diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d97ef294125..c9065ad5c9f 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -2,6 +2,7 @@ * 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)) * 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)) +* 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)) ### Fixed diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index c6df0d4dd0e..327c8bd3593 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -442,6 +442,7 @@ type TraitConstraintSolution = | TTraitSolved of minfo: MethInfo * minst: TypeInst * staticTyOpt: TType option | TTraitSolvedRecdProp of fieldInfo: RecdFieldInfo * isSetProp: bool | TTraitSolvedAnonRecdProp of anonRecdTypeInfo: AnonRecdTypeInfo * typeInst: TypeInst * index: int + | TTraitSolvedField of ty: TType * fieldInfo: ILFieldInfo * isSetField: bool let BakedInTraitConstraintNames = [ "op_Division" ; "op_Multiply"; "op_Addition" @@ -2006,9 +2007,48 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload else None + let fieldSearch = + if g.langVersion.SupportsFeature LanguageFeature.SupportILFieldsInSRTP then + let isGet = nm.StartsWithOrdinal("get_") + let isSet = nm.StartsWithOrdinal("set_") + + if not isRigid && ((argTys.IsEmpty && isGet) || isSet) then + let fieldNm = nm[4..] + + let fields = + [| for ty in supportTys do + let item = + TryFindIntrinsicNamedItemOfType + csenv.InfoReader + (fieldNm, AccessibleFromEverywhere, false) + FindMemberFlag.IgnoreOverrides + m + ty + + match item with + | Some(ILFieldItem [ ilfinfo ]) when + ilfinfo.IsStatic = (not memFlags.IsInstance) + && (isGet || not ilfinfo.IsInitOnly) + && IsILFieldInfoAccessible g amap m AccessibleFromEverywhere ilfinfo + && ilfinfo.LiteralValue.IsNone + && not ilfinfo.IsSpecialName + -> + yield (ilfinfo, isSet) + | _ -> () |] + + match fields with + | [| (ilfinfo, isSet) |] -> + let ty = ilfinfo.FieldType(amap, m) + Some(ty, ilfinfo, isSet) + | _ -> None + else + None + else + None + // Now check if there are no feasible solutions at all - match minfos, recdPropSearch, anonRecdPropSearch with - | [], None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo -> + match minfos, recdPropSearch, anonRecdPropSearch, fieldSearch with + | [], None, None, None when MemberConstraintIsReadyForStrongResolution csenv traitInfo -> if supportTys |> List.exists (isFunTy g) then return! ErrorD (ConstraintSolverError(FSComp.SR.csExpectTypeWithOperatorButGivenFunction(ConvertValLogicalNameToDisplayNameCore nm), m, m2)) elif supportTys |> List.exists (isAnyTupleTy g) then @@ -2069,36 +2109,69 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload (fun (a, _) -> Option.isSome a) (fun trace -> ResolveOverloading csenv (WithTrace trace) nm ndeep (Some traitInfo) CallerArgs.Empty AccessibleFromEverywhere calledMethGroup false (Some (MustEqual retTy))) - match anonRecdPropSearch, recdPropSearch, methOverloadResult with - | Some (anonInfo, tinst, i), None, None -> - // OK, the constraint is solved by a record property. Assert that the return types match. + match anonRecdPropSearch, recdPropSearch, fieldSearch, methOverloadResult with + | _, _, _, Some(calledMeth: CalledMeth<_>) -> + // Method/property has highest priority — wins even if a field also matched. + let minfo = calledMeth.Method + + do! errors + let isInstance = minfo.IsInstance + + if isInstance <> memFlags.IsInstance then + return! + if isInstance then + ErrorD( + ConstraintSolverError( + FSComp.SR.csMethodFoundButIsNotStatic ( + (NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), + (ConvertValLogicalNameToDisplayNameCore nm), + nm + ), + m, + m2 + ) + ) + else + ErrorD( + ConstraintSolverError( + FSComp.SR.csMethodFoundButIsStatic ( + (NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), + (ConvertValLogicalNameToDisplayNameCore nm), + nm + ), + m, + m2 + ) + ) + else + do! CheckMethInfoAttributes g m None minfo + return TTraitSolved(minfo, calledMeth.CalledTyArgs, calledMeth.OptionalStaticType) + + | Some(anonInfo, tinst, i), _, _, None -> + // Anonymous record property — wins over record properties and fields. let rty2 = List.item i tinst do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2 return TTraitSolvedAnonRecdProp(anonInfo, tinst, i) - | None, Some (rfinfo, isSetProp), None -> - // OK, the constraint is solved by a record property. Assert that the return types match. + | None, Some(rfinfo, isSetProp), _, None -> + // Record property — wins over fields. let rty2 = if isSetProp then g.unit_ty else rfinfo.FieldType do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2 return TTraitSolvedRecdProp(rfinfo, isSetProp) - | None, None, Some (calledMeth: CalledMeth<_>) -> - // OK, the constraint is solved. - let minfo = calledMeth.Method + | None, None, Some(ty, ilfinfo, isSet), None -> + // IL field — lowest priority, only when nothing else matched. + let rty2 = if isSet then g.unit_ty else ty + do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace retTy rty2 - do! errors - let isInstance = minfo.IsInstance - if isInstance <> memFlags.IsInstance then - return! - if isInstance then - ErrorD(ConstraintSolverError(FSComp.SR.csMethodFoundButIsNotStatic((NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), (ConvertValLogicalNameToDisplayNameCore nm), nm), m, m2 )) - else - ErrorD(ConstraintSolverError(FSComp.SR.csMethodFoundButIsStatic((NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), (ConvertValLogicalNameToDisplayNameCore nm), nm), m, m2 )) - else - do! CheckMethInfoAttributes g m None minfo - return TTraitSolved (minfo, calledMeth.CalledTyArgs, calledMeth.OptionalStaticType) + if isSet then + match argTys with + | [ argTy ] -> do! SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace argTy ty + | _ -> () - | _ -> + return TTraitSolvedField(ty, ilfinfo, isSet) + + | _ -> do! AddUnsolvedMemberConstraint csenv ndeep m2 trace permitWeakResolution ignoreUnresolvedOverload traitInfo errors return TTraitUnsolved } @@ -2167,6 +2240,11 @@ and RecordMemberConstraintSolution css m trace traitInfo traitConstraintSln = TransactMemberConstraintSolution traitInfo trace sln ResultD true + | TTraitSolvedField (ty, ilfinfo, isSet) -> + let sln = ILFieldSln(ty, ilfinfo.TypeInst, ilfinfo.ILFieldRef, ilfinfo.IsStatic, isSet) + TransactMemberConstraintSolution traitInfo trace sln + ResultD true + /// Convert a MethInfo into the data we save in the TAST and MemberConstraintSolutionOfMethInfo css m minfo minst staticTyOpt = #if !NO_TYPEPROVIDERS diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index 285dfa4fa8c..ce6c1bdb64e 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -2164,7 +2164,7 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs = let sln = match traitInfo.Solution with - | None -> Choice5Of5() + | None -> Choice6Of6() | Some sln -> // Given the solution information, reconstruct the MethInfo for the solution @@ -2179,25 +2179,54 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs = | Some ilActualTypeRef -> let actualTyconRef = ImportILTypeRef amap m ilActualTypeRef MethInfo.CreateILExtensionMeth(amap, m, origTy, actualTyconRef, None, mdef) - Choice1Of5 (ilMethInfo, minst, staticTyOpt) + Choice1Of6 (ilMethInfo, minst, staticTyOpt) | FSMethSln(ty, vref, minst, staticTyOpt) -> - Choice1Of5 (FSMeth(g, ty, vref, None), minst, staticTyOpt) + Choice1Of6 (FSMeth(g, ty, vref, None), minst, staticTyOpt) | FSRecdFieldSln(tinst, rfref, isSetProp) -> - Choice2Of5 (tinst, rfref, isSetProp) + Choice2Of6 (tinst, rfref, isSetProp) | FSAnonRecdFieldSln(anonInfo, tinst, i) -> - Choice3Of5 (anonInfo, tinst, i) + Choice3Of6 (anonInfo, tinst, i) | ClosedExprSln expr -> - Choice4Of5 expr + Choice4Of6 expr + + | ILFieldSln(ty, tinst, ilfref, isStatic, isSet) -> + Choice5Of6 (ty, tinst, ilfref, isStatic, isSet) | BuiltInSln -> - Choice5Of5 () + Choice6Of6 () match sln with - | Choice1Of5(minfo, methArgTys, staticTyOpt) -> + | Choice5Of6(ty, tinst, ilfref, isStatic, isSet) -> + let declaringTyconRef = ImportILTypeRef amap m ilfref.DeclaringTypeRef + let isStruct = isStructTy g (generalizedTyconRef g declaringTyconRef) + let boxity = if isStruct then ILBoxity.AsValue else ILBoxity.AsObject + let fspec = mkILFieldSpec (ilfref, mkILNamedTy boxity ilfref.DeclaringTypeRef []) + + match isStatic, isSet with + | false, false -> + // Instance getter: ldfld + Some(Expr.Op(TOp.ILAsm([ mkNormalLdfld fspec ], [ ty ]), tinst, argExprs, m)) + | false, true -> + // Instance setter: stfld (handle struct address-taking) + if isStruct && not (isByrefTy g (tyOfExpr g argExprs[0])) then + let wrap, h', _readonly, _writeonly = + mkExprAddrOfExpr g true false PossiblyMutates argExprs[0] None m + + Some(wrap (Expr.Op(TOp.ILAsm([ mkNormalStfld fspec ], []), tinst, [ h'; argExprs[1] ], m))) + else + Some(Expr.Op(TOp.ILAsm([ mkNormalStfld fspec ], []), tinst, argExprs, m)) + | true, false -> + // Static getter: ldsfld + Some(Expr.Op(TOp.ILAsm([ mkNormalLdsfld fspec ], [ ty ]), tinst, argExprs, m)) + | true, true -> + // Static setter: stsfld + Some(Expr.Op(TOp.ILAsm([ mkNormalStsfld fspec ], []), tinst, argExprs, m)) + + | Choice1Of6(minfo, methArgTys, staticTyOpt) -> let argExprs = // FIX for #421894 - typechecker assumes that coercion can be applied for the trait // calls arguments but codegen doesn't emit coercion operations @@ -2241,7 +2270,7 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs = else Some (MakeMethInfoCall amap m minfo methArgTys argExprs staticTyOpt) - | Choice2Of5 (tinst, rfref, isSet) -> + | Choice2Of6 (tinst, rfref, isSet) -> match isSet, rfref.RecdField.IsStatic, argExprs.Length with // static setter | true, true, 1 -> @@ -2271,17 +2300,17 @@ let GenWitnessExpr amap g m (traitInfo: TraitConstraintInfo) argExprs = | _ -> None - | Choice3Of5 (anonInfo, tinst, i) -> + | Choice3Of6 (anonInfo, tinst, i) -> let tupInfo = anonInfo.TupInfo if evalTupInfoIsStruct tupInfo && isByrefTy g (tyOfExpr g argExprs[0]) then Some (mkAnonRecdFieldGetViaExprAddr (anonInfo, argExprs[0], tinst, i, m)) else Some (mkAnonRecdFieldGet g (anonInfo, argExprs[0], tinst, i, m)) - | Choice4Of5 expr -> + | Choice4Of6 expr -> Some (MakeApplicationAndBetaReduce g (expr, tyOfExpr g expr, [], argExprs, m)) - | Choice5Of5 () -> + | Choice6Of6 () -> match traitInfo.Solution with | None -> None // the trait has been generalized | Some _-> diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index a4007147b9a..87dd16c8138 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1809,6 +1809,7 @@ featureWarnWhenFunctionValueUsedAsInterpolatedStringArg,"Warn when a function va featureMethodOverloadsCache,"Support for caching method overload resolution results for improved compilation performance." featureImplicitDIMCoverage,"Implicit dispatch slot coverage for default interface member implementations" featurePreprocessorElif,"#elif preprocessor directive" +featureSupportILFieldsInSRTP,"Support for .NET fields in SRTP member constraints" 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" 3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'." 3882,lexHashElifMustBeFirst,"#elif directive must appear as the first non-whitespace character on a line" diff --git a/src/Compiler/FSStrings.resx b/src/Compiler/FSStrings.resx index 9e471249843..a5cd44be193 100644 --- a/src/Compiler/FSStrings.resx +++ b/src/Compiler/FSStrings.resx @@ -1155,4 +1155,7 @@ No constructors are available for the type '{0}' + + Support for .NET fields in SRTP member constraints + \ No newline at end of file diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 1bc31837052..8c93362d4c3 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -108,6 +108,7 @@ type LanguageFeature = | MethodOverloadsCache | ImplicitDIMCoverage | PreprocessorElif + | SupportILFieldsInSRTP /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -258,6 +259,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) // F# preview (still preview in 10.0) LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work LanguageFeature.MethodOverloadsCache, previewVersion // Performance optimization for overload resolution + LanguageFeature.SupportILFieldsInSRTP, previewVersion LanguageFeature.ImplicitDIMCoverage, languageVersion110 ] @@ -453,6 +455,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) | LanguageFeature.MethodOverloadsCache -> FSComp.SR.featureMethodOverloadsCache () | LanguageFeature.ImplicitDIMCoverage -> FSComp.SR.featureImplicitDIMCoverage () | LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif () + | LanguageFeature.SupportILFieldsInSRTP -> FSComp.SR.featureSupportILFieldsInSRTP () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index fd6182c9d3e..757cb725640 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -99,6 +99,7 @@ type LanguageFeature = | MethodOverloadsCache | ImplicitDIMCoverage | PreprocessorElif + | SupportILFieldsInSRTP /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index fed644eeb61..2d18538782b 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -605,6 +605,7 @@ module internal SymbolHelpers = | Some (TraitConstraintSln.FSRecdFieldSln _) | Some (TraitConstraintSln.FSAnonRecdFieldSln _) | Some (TraitConstraintSln.ClosedExprSln _) + | Some (TraitConstraintSln.ILFieldSln _) | Some TraitConstraintSln.BuiltInSln | None -> GetXmlCommentForItemAux None infoReader m item diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index b08823c2734..d3798e7a440 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -2663,6 +2663,16 @@ type TraitConstraintSln = /// Indicates a trait is solved by a 'fake' instance of an operator, like '+' on integers | BuiltInSln + /// ILFieldSln(ty, tinst, ilfref, isStatic, isSet) + /// + /// Indicates a trait is solved by a .NET IL field. + /// ty -- the F# type of the field + /// tinst -- the type instantiation of the declaring type + /// ilfref -- IL field reference (declaring type, name, IL type) + /// isStatic -- whether the field is static + /// isSet -- whether this is a set operation + | ILFieldSln of ty: TType * tinst: TypeInst * ilfref: ILFieldRef * isStatic: bool * isSet: bool + // %+A formatting is used, so this is not needed //[] //member x.DebugText = x.ToString() diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 3fe7c5b1c90..563b455a8d8 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -1833,6 +1833,16 @@ type TraitConstraintSln = /// Indicates a trait is solved by a 'fake' instance of an operator, like '+' on integers | BuiltInSln + /// ILFieldSln(ty, tinst, ilfref, isStatic, isSet) + /// + /// Indicates a trait is solved by a .NET IL field. + /// ty -- the F# type of the field + /// tinst -- the type instantiation of the declaring type + /// ilfref -- IL field reference (declaring type, name, IL type) + /// isStatic -- whether the field is static + /// isSet -- whether this is a set operation + | ILFieldSln of ty: TType * tinst: TypeInst * ilfref: ILFieldRef * isStatic: bool * isSet: bool + override ToString: unit -> string /// The partial information used to index the methods of all those in a ModuleOrNamespace. diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 9ddb334b97d..d87d435589b 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -287,6 +287,8 @@ and remapTraitInfo tyenv (TTrait(tys, nm, flags, argTys, retTy, source, slnCell) FSRecdFieldSln(remapTypesAux tyenv tinst, remapRecdFieldRef tyenv.tyconRefRemap rfref, isSet) | FSAnonRecdFieldSln(anonInfo, tinst, n) -> FSAnonRecdFieldSln(anonInfo, remapTypesAux tyenv tinst, n) + | ILFieldSln(ty, tinst, ilfref, isStatic, isSet) -> + ILFieldSln(remapTypeAux tyenv ty, remapTypesAux tyenv tinst, ilfref, isStatic, isSet) | BuiltInSln -> BuiltInSln | ClosedExprSln e -> @@ -2421,6 +2423,8 @@ and accFreeInTraitSln opts sln acc = accFreeInTypes opts tinst acc | FSRecdFieldSln(tinst, _rfref, _isSet) -> accFreeInTypes opts tinst acc + | ILFieldSln(ty, tinst, _, _, _) -> + accFreeInType opts ty (accFreeInTypes opts tinst acc) | BuiltInSln -> acc | ClosedExprSln _ -> acc // nothing to accumulate because it's a closed expression referring only to erasure of provided method calls diff --git a/src/Compiler/TypedTree/TypedTreePickle.fs b/src/Compiler/TypedTree/TypedTreePickle.fs index b5bd769de7f..36787f8c127 100644 --- a/src/Compiler/TypedTree/TypedTreePickle.fs +++ b/src/Compiler/TypedTree/TypedTreePickle.fs @@ -2129,6 +2129,9 @@ let p_trait_sln sln st = | FSMethSln(a, b, c, Some d) -> p_byte 7 st p_tup4 p_ty (p_vref "trait") p_tys p_ty (a, b, c, d) st + | ILFieldSln(a, b, c, d, e) -> + p_byte 8 st + p_tup5 p_ty p_tys p_ILFieldRef p_bool p_bool (a, b, c, d, e) st let p_trait (TTrait(a, b, c, d, e, _, f)) st = p_tup6 p_tys p_string p_MemberFlags p_tys (p_option p_ty) (p_option p_trait_sln) (a, b, c, d, e, f.Value) st @@ -2165,6 +2168,9 @@ let u_trait_sln st = | 7 -> let a, b, c, d = u_tup4 u_ty u_vref u_tys u_ty st FSMethSln(a, b, c, Some d) + | 8 -> + let a, b, c, d, e = u_tup5 u_ty u_tys u_ILFieldRef u_bool u_bool st + ILFieldSln(a, b, c, d, e) | _ -> ufailwith st "u_trait_sln" let u_trait st = diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 6896c33a6a4..e5e748f6bd8 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -667,6 +667,11 @@ reprezentace struktury aktivních vzorů + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Rozšíření správce závislostí {0} nešlo načíst. Zpráva: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 3966a59a853..ebbd93f3cf2 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -667,6 +667,11 @@ Strukturdarstellung für aktive Muster + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Die Abhängigkeits-Manager-Erweiterung "{0}" konnte nicht geladen werden. Meldung: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 08828606dd6..ab81b13ada6 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -667,6 +667,11 @@ representación de struct para modelos activos + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ No se pudo cargar la extensión del administrador de dependencias {0}. Mensaje: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 5a28ec15953..8b8c44b944a 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -667,6 +667,11 @@ représentation de structure pour les modèles actifs + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Impossible de charger l'extension du gestionnaire de dépendances {0}. Message : {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 5dead052c6a..e53ffee528e 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -667,6 +667,11 @@ rappresentazione struct per criteri attivi + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Non è stato possibile caricare l'estensione {0} di gestione delle dipendenze. Messaggio: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index f491fb0c4c5..21d518e4d12 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -667,6 +667,11 @@ アクティブなパターンの構造体表現 + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ 依存関係マネージャーの拡張機能 {0} を読み込むことができませんでした。メッセージ: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index f8185fb2a22..2cc30129d95 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -667,6 +667,11 @@ 활성 패턴에 대한 구조체 표현 + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ 종속성 관리자 확장 {0}을(를) 로드할 수 없습니다. 메시지: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 7e81e135eeb..12cd47b6809 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -667,6 +667,11 @@ reprezentacja struktury aktywnych wzorców + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Nie można załadować rozszerzenia menedżera zależności {0}. Komunikat: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 22a5db4504c..266da25884f 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -667,6 +667,11 @@ representação estrutural para padrões ativos + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Não foi possível carregar a extensão do gerenciador de dependências {0}. Mensagem: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 8f5220ba47e..f97e73b496f 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -667,6 +667,11 @@ представление структуры для активных шаблонов + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ Не удалось загрузить расширение диспетчера зависимостей {0}. Сообщение: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 17509827124..8d69fc49c7b 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -667,6 +667,11 @@ etkin desenler için yapı gösterimi + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ {0} bağımlılık yöneticisi uzantısı yüklenemedi. İleti: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index bf26625f8ec..ff3bc9185ed 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -667,6 +667,11 @@ 活动模式的结构表示形式 + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ 无法加载依赖项管理器扩展 {0}。消息: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 2a63e5ad753..8a67ec16f5f 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -667,6 +667,11 @@ 現用模式的結構表示法 + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + Support ValueOption as valid type for optional member parameters Support ValueOption as valid type for optional member parameters @@ -8952,12 +8957,12 @@ 無法載入相依性管理員延伸模組 {0}。訊息: {1} - + Warn when a function value is used as an interpolated string argument Warn when a function value is used as an interpolated string argument - + This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. diff --git a/src/Compiler/xlf/FSStrings.cs.xlf b/src/Compiler/xlf/FSStrings.cs.xlf index 80fc8d575a2..ac90c964085 100644 --- a/src/Compiler/xlf/FSStrings.cs.xlf +++ b/src/Compiler/xlf/FSStrings.cs.xlf @@ -1732,6 +1732,11 @@ klíčové slovo and! + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.de.xlf b/src/Compiler/xlf/FSStrings.de.xlf index cad73cb05c3..f83447b541b 100644 --- a/src/Compiler/xlf/FSStrings.de.xlf +++ b/src/Compiler/xlf/FSStrings.de.xlf @@ -1732,6 +1732,11 @@ Schlüsselwort "and!" + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.es.xlf b/src/Compiler/xlf/FSStrings.es.xlf index e262a1eae44..519a060610f 100644 --- a/src/Compiler/xlf/FSStrings.es.xlf +++ b/src/Compiler/xlf/FSStrings.es.xlf @@ -1732,6 +1732,11 @@ palabra clave 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.fr.xlf b/src/Compiler/xlf/FSStrings.fr.xlf index 0a265c96bea..422a59a5ac8 100644 --- a/src/Compiler/xlf/FSStrings.fr.xlf +++ b/src/Compiler/xlf/FSStrings.fr.xlf @@ -1732,6 +1732,11 @@ mot clé 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.it.xlf b/src/Compiler/xlf/FSStrings.it.xlf index b0ceff4e83a..2f9e23da410 100644 --- a/src/Compiler/xlf/FSStrings.it.xlf +++ b/src/Compiler/xlf/FSStrings.it.xlf @@ -1732,6 +1732,11 @@ parola chiave 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.ja.xlf b/src/Compiler/xlf/FSStrings.ja.xlf index 58439655441..e3ad66d0747 100644 --- a/src/Compiler/xlf/FSStrings.ja.xlf +++ b/src/Compiler/xlf/FSStrings.ja.xlf @@ -1732,6 +1732,11 @@ キーワード 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.ko.xlf b/src/Compiler/xlf/FSStrings.ko.xlf index 84886e9c6ca..c27a4511c1e 100644 --- a/src/Compiler/xlf/FSStrings.ko.xlf +++ b/src/Compiler/xlf/FSStrings.ko.xlf @@ -1732,6 +1732,11 @@ 키워드 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.pl.xlf b/src/Compiler/xlf/FSStrings.pl.xlf index 04e2db874cd..c053be7bed1 100644 --- a/src/Compiler/xlf/FSStrings.pl.xlf +++ b/src/Compiler/xlf/FSStrings.pl.xlf @@ -1732,6 +1732,11 @@ słowo kluczowe „and!” + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.pt-BR.xlf b/src/Compiler/xlf/FSStrings.pt-BR.xlf index ecce364bb99..2ca71ae0bf5 100644 --- a/src/Compiler/xlf/FSStrings.pt-BR.xlf +++ b/src/Compiler/xlf/FSStrings.pt-BR.xlf @@ -1732,6 +1732,11 @@ palavra-chave 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.ru.xlf b/src/Compiler/xlf/FSStrings.ru.xlf index fff4ab3f12d..0829384601a 100644 --- a/src/Compiler/xlf/FSStrings.ru.xlf +++ b/src/Compiler/xlf/FSStrings.ru.xlf @@ -1732,6 +1732,11 @@ ключевое слово "and!" + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.tr.xlf b/src/Compiler/xlf/FSStrings.tr.xlf index 69a73dfd9fd..5defb99f28b 100644 --- a/src/Compiler/xlf/FSStrings.tr.xlf +++ b/src/Compiler/xlf/FSStrings.tr.xlf @@ -1732,6 +1732,11 @@ 'and!' anahtar sözcüğü + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.zh-Hans.xlf b/src/Compiler/xlf/FSStrings.zh-Hans.xlf index f2f98d4e788..1ff8f8ddce1 100644 --- a/src/Compiler/xlf/FSStrings.zh-Hans.xlf +++ b/src/Compiler/xlf/FSStrings.zh-Hans.xlf @@ -1732,6 +1732,11 @@ 关键字 "and!" + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSStrings.zh-Hant.xlf b/src/Compiler/xlf/FSStrings.zh-Hant.xlf index 571fa7b1446..55b41d4d687 100644 --- a/src/Compiler/xlf/FSStrings.zh-Hant.xlf +++ b/src/Compiler/xlf/FSStrings.zh-Hant.xlf @@ -1732,6 +1732,11 @@ 關鍵字 'and!' + + Support for .NET fields in SRTP member constraints + Support for .NET fields in SRTP member constraints + + \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs index c560008da0c..e804be17db2 100644 --- a/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs +++ b/tests/FSharp.Compiler.ComponentTests/ConstraintSolver/MemberConstraints.fs @@ -113,3 +113,543 @@ ignore ["1" .. "42"] |> shouldFail |> withSingleDiagnostic (Error 1, Line 2, Col 9, Line 2, Col 12, "The type 'string' does not support the operator 'op_Range'") + + [] + let ``SRTP can resolve C# class instance field getter`` () = + let csLib = + CSharp """ +public class Foo { + public string Id = "hello"; +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline getId (x: ^T) = (^T : (member Id : string) x) + +[] +let main _ = + printf "%s" (getId (Foo())) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "hello" + + [] + let ``SRTP can resolve C# class instance field setter`` () = + let csLib = + CSharp """ +public class Foo { + public string Id = "initial"; +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline setId (x: ^T) (v: string) = (^T : (member set_Id : string -> unit) (x, v)) +let inline getId (x: ^T) = (^T : (member Id : string) x) + +[] +let main _ = + let foo = Foo() + setId foo "mutated" + printf "%s" (getId foo) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "mutated" + + [] + let ``SRTP can resolve C# struct instance field getter`` () = + let csLib = + CSharp """ +public struct Point { + public int X; + public int Y; + public Point(int x, int y) { X = x; Y = y; } +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline getX (x: ^T) = (^T : (member X : int) x) + +[] +let main _ = + let p = Point(3, 4) + printf "%d" (getX p) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "3" + + [] + let ``SRTP can resolve C# struct instance field setter`` () = + let csLib = + CSharp """ +public struct Point { + public int X; + public int Y; + public Point(int x, int y) { X = x; Y = y; } +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline setX (x: ^T) (v: int) = (^T : (member set_X : int -> unit) (x, v)) + +[] +let main _ = + let mutable p = Point(3, 4) + setX p 99 + printf "ok" + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "ok" + + [] + let ``SRTP C# struct field setter on mutable binding creates defensive copy`` () = + // On a mutable binding, inline expansion of the SRTP setter copies the struct + // value into an immutable parameter binding. The mutation applies to the copy, + // not the original. This is the expected struct value-type semantics for SRTP + // inline expansion — contrast with the immutable binding test where the compiler + // aliases instead of copying, allowing mutation to persist. + let csLib = + CSharp """ +public struct Point { + public int X; + public int Y; + public Point(int x, int y) { X = x; Y = y; } +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline setX (x: ^T) (v: int) = (^T : (member set_X : int -> unit) (x, v)) + +[] +let main _ = + let mutable p = Point(3, 4) + setX p 99 + printf "%d" p.X + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "3" + + [] + let ``SRTP can resolve C# class static field getter`` () = + let csLib = + CSharp """ +public class Counter { + public static int Count = 42; +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline getCount<'T when ^T : (static member Count : int)> () = (^T : (static member Count : int) ()) + +[] +let main _ = + printf "%d" (getCount ()) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "42" + + [] + let ``SRTP can resolve C# class static field setter`` () = + let csLib = + CSharp """ +public class Counter { + public static int Count = 0; +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline setCount<'T when ^T : (static member set_Count : int -> unit)> (v: int) = (^T : (static member set_Count : int -> unit) v) +let inline getCount<'T when ^T : (static member Count : int)> () = (^T : (static member Count : int) ()) + +[] +let main _ = + setCount 99 + printf "%d" (getCount ()) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "99" + + [] + let ``SRTP can resolve generic C# class field`` () = + let csLib = + CSharp """ +public class Wrapper { + public T Value; + public Wrapper(T val) { Value = val; } +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline getValue (x: ^T) = (^T : (member Value : 'R) x) + +[] +let main _ = + let ws = Wrapper("hello") + let wi = Wrapper(42) + printf "%s %d" (getValue ws) (getValue wi) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "hello 42" + + [] + let ``SRTP inline function works with both C# field and F# record`` () = + let csLib = + CSharp """ +public class CSharpObj { + public string Name = "csharp"; +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +type FSharpRec = { Name: string } + +let inline getName (x: ^T) = (^T : (member Name : string) x) + +[] +let main _ = + let cs = CSharpObj() + let fs = { Name = "fsharp" } + printf "%s %s" (getName cs) (getName fs) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "csharp fsharp" + + [] + let ``F# record field SRTP still works with langversion preview`` () = + let fsApp = + FSharp """ +open System + +type MyRec = { Value: int } + +let inline getValue (x: ^T) = (^T : (member Value : int) x) + +[] +let main _ = + let r = { Value = 123 } + printf "%d" (getValue r) + 0 + """ + |> asExe + |> withLangVersionPreview + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "123" + + [] + let ``SRTP can resolve C# readonly field getter`` () = + let csLib = + CSharp """ +public class Config { + public readonly string Key = "secret"; +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline getKey (x: ^T) = (^T : (member Key : string) x) + +[] +let main _ = + printf "%s" (getKey (Config())) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "secret" + + [] + let ``SRTP rejects C# readonly field setter`` () = + let csLib = + CSharp """ +public class Config { + public readonly string Key = "secret"; +} + """ + |> withName "csLib" + + FSharp """ +open System + +let inline setKey (x: ^T) (v: string) = (^T : (member set_Key : string -> unit) (x, v)) + +let main () = + setKey (Config()) "changed" + """ + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compile + |> shouldFail + + [] + let ``SRTP does not resolve C# private field`` () = + let csLib = + CSharp """ +public class Secret { + private string Hidden = "nope"; +} + """ + |> withName "csLib" + + FSharp """ +open System + +let inline getHidden (x: ^T) = (^T : (member Hidden : string) x) + +let main () = + getHidden (Secret()) |> ignore + """ + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compile + |> shouldFail + + [] + let ``SRTP does not resolve C# const literal field`` () = + let csLib = + CSharp """ +public class Constants { + public const int MaxValue = 100; +} + """ + |> withName "csLib" + + FSharp """ +open System + +let inline getMax<'T when ^T : (static member MaxValue : int)> () = (^T : (static member MaxValue : int) ()) + +let main () = + getMax () |> ignore + """ + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compile + |> shouldFail + + [] + let ``SRTP resolves property and field with same inline function`` () = + let csLib = + CSharp + """ +public class HasProperty { + public string Id { get { return "property"; } } +} + +public class HasField { + public string Id = "field"; +} + """ + |> withName "csLib" + + FSharp + """ +open System + +let inline getId (x: ^T) = (^T : (member Id : string) x) + +[] +let main _ = + printf "%s,%s" (getId (HasProperty())) (getId (HasField())) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compileAndRun + |> shouldSucceed + |> verifyOutput "property,field" + + [] + let ``SRTP method wins over field when both exist on same type`` () = + let csLib = + CSharp + """ +public class HasBoth { + public int Value = 100; + public int get_Value() { return 42; } +} + """ + |> withName "csLib" + + FSharp + """ +open System + +let inline getValue (x: ^T) = (^T : (member get_Value : unit -> int) x) + +[] +let main _ = + printf "%d" (getValue (HasBoth())) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compileAndRun + |> shouldSucceed + |> verifyOutput "42" + + [] + let ``SRTP field type mismatch causes error`` () = + let csLib = + CSharp """ +public class Foo { + public string Name = "hello"; +} + """ + |> withName "csLib" + + FSharp """ +open System + +let inline getName (x: ^T) = (^T : (member Name : int) x) + +let main () = + getName (Foo()) |> ignore + """ + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compile + |> shouldFail + + [] + let ``SRTP does not resolve special-name field`` () = + // C# enum's value__ field is public but marked specialname+rtspecialname in IL. + // The solver's fieldSearch must filter it out via `not ilfinfo.IsSpecialName`. + let csLib = + CSharp """ +public enum Color { + Red = 1, + Green = 2, + Blue = 3 +} + """ + |> withName "csLib" + + FSharp """ +open System + +let inline getUnderlying (x: ^T) = (^T : (member value__ : int) x) + +let main () = + getUnderlying Color.Red |> ignore + """ + |> withLangVersionPreview + |> withReferences [ csLib ] + |> compile + |> shouldFail + + [] + let ``SRTP C# struct field setter on immutable binding mutates value`` () = + // Due to how SRTP inline expansion works, setting a C# struct field via + // SRTP mutates the original value even on an immutable binding. This happens + // because: (1) C# structs with mutable fields are incorrectly assumed + // "readonly" by isRecdOrUnionOrStructTyconRefDefinitelyMutable (which only + // checks F# fields), causing CanTakeAddressOfImmutableVal to succeed, and + // (2) the readonly flag returned by mkExprAddrOfExpr is ignored in the + // ILFieldSln codegen. This test documents the current behavior. + let csLib = + CSharp """ +public struct Point { + public int X; + public int Y; + public Point(int x, int y) { X = x; Y = y; } +} + """ + |> withName "csLib" + + let fsApp = + FSharp """ +open System + +let inline setX (x: ^T) (v: int) = (^T : (member set_X : int -> unit) (x, v)) +let inline getX (x: ^T) : int = (^T : (member X : int) x) + +[] +let main _ = + let p = Point(3, 4) + setX p 99 + printf "%d" (getX p) + 0 + """ + |> asExe + |> withLangVersionPreview + |> withReferences [ csLib ] + + fsApp |> compileAndRun |> shouldSucceed |> verifyOutput "99" diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs index 3463bb05d20..77f49cfcd10 100644 --- a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs +++ b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs @@ -2,6 +2,8 @@ open CompilerCompatLib open CompilerCompatApp open System +type TestRecord = { Value: string } + [] let main _argv = try @@ -44,7 +46,28 @@ let main _argv = if processed = "Processed: X=123, Y=test" then printfn "SUCCESS: All compiler compatibility tests passed" - 0 + + // Test SRTP member constraint compatibility + let srtpResult = Library.getMemberValue { Value = "compat-test" } + printfn "SRTP getMemberValue result: %s" srtpResult + + if srtpResult <> "compat-test" then + printfn "ERROR: SRTP getMemberValue result doesn't match expected" + 1 + else + printfn "SUCCESS: SRTP member constraint compatibility test passed" + + // Test SRTP struct field constraint compatibility + let holder = Library.FieldHolder(99) + let fieldResult = Library.getFieldValue holder + printfn "SRTP getFieldValue result: %d" fieldResult + + if fieldResult <> 99 then + printfn "ERROR: SRTP getFieldValue result doesn't match expected" + 1 + else + printfn "SUCCESS: SRTP struct field constraint compatibility test passed" + 0 else printfn "ERROR: Processed result doesn't match expected" 1 diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs index e0b4f380802..4422c3c399f 100644 --- a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs +++ b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs @@ -14,4 +14,22 @@ module Library = /// Function that takes an anonymous record as parameter let processAnonymousRecord (record: {| X: int; Y: string |}) = - sprintf "Processed: X=%d, Y=%s" record.X record.Y \ No newline at end of file + sprintf "Processed: X=%d, Y=%s" record.X record.Y + + /// Inline function using SRTP member constraint that can resolve to a field or property + let inline getMemberValue (x: ^T) : string = (^T: (member Value: string) x) + + // F# struct with explicit public field for SRTP field constraint testing. + // Note: This exercises the F# record-field SRTP path (FSRecdFieldSln), not the + // IL field path (ILFieldSln). The ILFieldSln path requires the preview-only + // feature SupportILFieldsInSRTP and a non-F# type (e.g., C# class with public field), + // which cannot be tested cross-version because released SDK compilers don't support + // the preview feature. The ILFieldSln pickle tag (8) is verified by 26+ component + // tests in tests/FSharp.Compiler.ComponentTests (ConstraintSolver/MemberConstraints). + [] + type FieldHolder = + val mutable FieldValue: int + new(v) = { FieldValue = v } + + /// Inline function using SRTP to read a struct field value + let inline getFieldValue (x: ^T) : int = (^T: (member FieldValue: int) x) \ No newline at end of file