From 874e4890168bab300e2158353527f4c19e7d6d2d Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Fri, 19 Jun 2026 11:45:00 +0100 Subject: [PATCH 01/10] Allow constructing F# records via their all-fields constructor (FS-1073) Records compile to a class whose all-fields constructor is callable from C# (new MyRecord(a, b)) but not from F#, which only allows { Field = ... } syntax. This adds a RecordConstructorSyntax preview feature that surfaces that constructor to F# too, supporting positional and named arguments. Implemented via a new MethInfo.RecdAllFieldsCtor case surfaced by InfoReader for record tycons; it elaborates through the existing mkRecordExpr path, so there is no codegen or overload-resolution change. Accessibility mirrors { } construction (the ctor is no more accessible than the record's representation/fields), so the C# behaviour of a public IL constructor bypassing a private record is not inherited. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Compiler/Checking/AccessibilityLogic.fs | 7 ++ src/Compiler/Checking/AttributeChecking.fs | 3 + src/Compiler/Checking/ConstraintSolver.fs | 5 +- src/Compiler/Checking/InfoReader.fs | 29 +++-- src/Compiler/Checking/MethodCalls.fs | 17 ++- src/Compiler/Checking/NameResolution.fs | 4 +- src/Compiler/Checking/NicePrint.fs | 7 +- .../Checking/OverloadResolutionCache.fs | 1 + src/Compiler/Checking/infos.fs | 45 ++++++- src/Compiler/Checking/infos.fsi | 3 + src/Compiler/FSComp.txt | 1 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/Symbols/SymbolHelpers.fs | 1 + src/Compiler/xlf/FSComp.txt.cs.xlf | 5 + src/Compiler/xlf/FSComp.txt.de.xlf | 5 + src/Compiler/xlf/FSComp.txt.es.xlf | 5 + src/Compiler/xlf/FSComp.txt.fr.xlf | 5 + src/Compiler/xlf/FSComp.txt.it.xlf | 5 + src/Compiler/xlf/FSComp.txt.ja.xlf | 5 + src/Compiler/xlf/FSComp.txt.ko.xlf | 5 + src/Compiler/xlf/FSComp.txt.pl.xlf | 5 + src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 + src/Compiler/xlf/FSComp.txt.ru.xlf | 5 + src/Compiler/xlf/FSComp.txt.tr.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 + .../Types/RecordTypes/RecordTypes.fs | 114 ++++++++++++++++++ 28 files changed, 290 insertions(+), 16 deletions(-) diff --git a/src/Compiler/Checking/AccessibilityLogic.fs b/src/Compiler/Checking/AccessibilityLogic.fs index 4995095a688..7b3ba81f5ff 100644 --- a/src/Compiler/Checking/AccessibilityLogic.fs +++ b/src/Compiler/Checking/AccessibilityLogic.fs @@ -356,6 +356,13 @@ let rec IsTypeAndMethInfoAccessible amap m accessDomainTy ad = function | FSMeth (_, _, vref, _) -> IsValAccessible ad vref | MethInfoWithModifiedReturnType(mi,_) -> IsTypeAndMethInfoAccessible amap m accessDomainTy ad mi | DefaultStructCtor(g, ty) -> IsTypeAccessible g amap m ad ty + | RecdAllFieldsCtor(g, ty) -> + // The synthesized all-fields constructor must be no more accessible than constructing the record + // with '{ ... }' syntax: require the type, its representation and every field to be accessible. + // This stops F# inheriting the C# behaviour where the IL constructor is public regardless of the + // record's 'private'/'internal' representation. + IsTypeAccessible g amap m ad ty && + ((tcrefOfAppTy g ty).TrueInstanceFieldsAsRefList |> List.forall (IsRecdFieldAccessible amap m ad)) #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, tpmb, _, m) as etmi -> let access = tpmb.PUntaint((fun mi -> ComputeILAccess mi.IsPublic mi.IsFamily mi.IsFamilyOrAssembly mi.IsFamilyAndAssembly), m) diff --git a/src/Compiler/Checking/AttributeChecking.fs b/src/Compiler/Checking/AttributeChecking.fs index 695eb4f3286..c7ae9643533 100755 --- a/src/Compiler/Checking/AttributeChecking.fs +++ b/src/Compiler/Checking/AttributeChecking.fs @@ -155,6 +155,7 @@ let rec GetAttribInfosOfMethod amap m minfo = | FSMeth (g, _, vref, _) -> vref.Attribs |> AttribInfosOfFS g | MethInfoWithModifiedReturnType(mi,_) -> GetAttribInfosOfMethod amap m mi | DefaultStructCtor _ -> [] + | RecdAllFieldsCtor _ -> [] #if !NO_TYPEPROVIDERS // TODO: provided attributes | ProvidedMeth (_, _mi, _, _m) -> @@ -191,6 +192,7 @@ let rec BindMethInfoAttributes m minfo f1 f2 f3 = | FSMeth (_, _, vref, _) -> f2 vref.Attribs | MethInfoWithModifiedReturnType(mi,_) -> BindMethInfoAttributes m mi f1 f2 f3 | DefaultStructCtor _ -> f2 [] + | RecdAllFieldsCtor _ -> f2 [] #if !NO_TYPEPROVIDERS | ProvidedMeth (_, mi, _, _) -> f3 (mi.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) #endif @@ -246,6 +248,7 @@ let rec MethInfoHasWellKnownAttribute g (m: range) (ilFlag: WellKnownILAttribute | ILMeth(_, ilMethInfo, _) -> ilMethInfo.RawMetadata.HasWellKnownAttribute(g, ilFlag) | FSMeth(_, _, vref, _) -> ValHasWellKnownAttribute g valFlag vref.Deref | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false | MethInfoWithModifiedReturnType(mi, _) -> MethInfoHasWellKnownAttribute g m ilFlag valFlag attribSpec mi #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> MethInfoHasAttribute g m attribSpec minfo diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index b739cede2de..bb47931fe3d 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -2185,9 +2185,12 @@ and MemberConstraintSolutionOfMethInfo css m minfo minst staticTyOpt = | MethInfoWithModifiedReturnType(mi,_) -> MemberConstraintSolutionOfMethInfo css m mi minst staticTyOpt - | MethInfo.DefaultStructCtor _ -> + | MethInfo.DefaultStructCtor _ -> error(InternalError("the default struct constructor was the unexpected solution to a trait constraint", m)) + | MethInfo.RecdAllFieldsCtor _ -> + error(InternalError("the record all-fields constructor was the unexpected solution to a trait constraint", m)) + #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> let g = amap.g diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index 121b087f5e5..ae14bc640cf 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -953,14 +953,22 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = else match tryTcrefOfAppTy g metadataTy with | ValueNone -> [] - | ValueSome tcref -> - tcref.MembersOfFSharpTyconByName - |> NameMultiMap.find ".ctor" - |> List.choose(fun vref -> - match vref.MemberInfo with - | Some membInfo when (membInfo.MemberFlags.MemberKind = SynMemberKind.Constructor) -> Some vref - | _ -> None) - |> List.map (fun x -> FSMeth(g, origTy, x, None)) + | ValueSome tcref -> + let declaredCtors = + tcref.MembersOfFSharpTyconByName + |> NameMultiMap.find ".ctor" + |> List.choose(fun vref -> + match vref.MemberInfo with + | Some membInfo when (membInfo.MemberFlags.MemberKind = SynMemberKind.Constructor) -> Some vref + | _ -> None) + |> List.map (fun x -> FSMeth(g, origTy, x, None)) + // An F# record exposes no *declared* constructor, but its synthesized all-fields constructor is + // callable from C# as 'new MyRecord(f1, f2, ...)'. Under the RecordConstructorSyntax feature we + // surface that same constructor to F# too (it elaborates to a record allocation - see BuildMethodCall). + if g.langVersion.SupportsFeature LanguageFeature.RecordConstructorSyntax && tcref.IsRecordTycon then + declaredCtors @ [ RecdAllFieldsCtor(g, origTy) ] + else + declaredCtors ) static member ExcludeHiddenOfMethInfos g amap m minfos = @@ -1264,6 +1272,11 @@ let rec GetXmlDocSigOfMethInfo (infoReader: InfoReader) m (minfo: MethInfo) = | ValueSome tcref -> Some(None, $"M:{tcref.CompiledRepresentationForNamedType.FullName}.#ctor") | _ -> None + | RecdAllFieldsCtor(g, ty) -> + match tryTcrefOfAppTy g ty with + | ValueSome tcref -> + Some(None, $"M:{tcref.CompiledRepresentationForNamedType.FullName}.#ctor") + | _ -> None #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> None diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index a9c394ee4e9..3a78b011543 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -1120,9 +1120,14 @@ let rec MakeMethInfoCall (amap: ImportMap) m (minfo: MethInfo) minst args static | MethInfoWithModifiedReturnType(mi,_) -> MakeMethInfoCall amap m mi minst args staticTyOpt - | DefaultStructCtor(_, ty) -> + | DefaultStructCtor(_, ty) -> mkDefault (m, ty) + | RecdAllFieldsCtor(g, ty) -> + let tcref = tcrefOfAppTy g ty + let tinst = argsOfAppTy g ty + mkRecordExpr g (RecdExpr, tcref, tinst, tcref.TrueInstanceFieldsAsRefList, args, m) + #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> let isProp = false // not necessarily correct, but this is only used post-creflect where this flag is irrelevant @@ -1264,9 +1269,15 @@ let rec BuildMethodCall tcVal g amap isMutable m isProp minfo valUseFlags minst else warning(Error(FSComp.SR.tcDefaultStructConstructorCall(), m)) else - if not (TypeHasDefaultValue g m ty) then + if not (TypeHasDefaultValue g m ty) then errorR(Error(FSComp.SR.tcDefaultStructConstructorCall(), m)) - mkDefault (m, ty), ty) + mkDefault (m, ty), ty + + // Build a record allocation from a call to the synthesized all-fields constructor of an F# record. + | RecdAllFieldsCtor (g, ty) -> + let tcref = tcrefOfAppTy g ty + let tinst = argsOfAppTy g ty + mkRecordExpr g (RecdExpr, tcref, tinst, tcref.TrueInstanceFieldsAsRefList, allArgs, m), ty) let ILFieldStaticChecks g amap infoReader ad m (finfo : ILFieldInfo) = CheckILFieldInfoAccessible g amap m ad finfo diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index b214d3b9e0d..69b3d1d8b98 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -704,7 +704,9 @@ let rec TrySelectExtensionMethInfoOfILExtMem m amap apparentTy (actualParent, mi | ProvidedMeth(amap,providedMeth,_,m) -> ProvidedMeth(amap, providedMeth, Some pri,m) |> Some #endif - | DefaultStructCtor _ -> + | DefaultStructCtor _ -> + None + | RecdAllFieldsCtor _ -> None /// Select from a list of extension methods diff --git a/src/Compiler/Checking/NicePrint.fs b/src/Compiler/Checking/NicePrint.fs index de8daffa7c5..62dcc97a7b5 100644 --- a/src/Compiler/Checking/NicePrint.fs +++ b/src/Compiler/Checking/NicePrint.fs @@ -1785,10 +1785,13 @@ module InfoMemberPrinting = let amap = infoReader.amap match methInfo with - | DefaultStructCtor _ -> - let prettyTyparInst, _ = PrettyTypes.PrettifyInst amap.g typarInst + | DefaultStructCtor _ -> + let prettyTyparInst, _ = PrettyTypes.PrettifyInst amap.g typarInst let resL = PrintTypes.layoutTyconRef denv methInfo.ApparentEnclosingTyconRef ^^ wordL punctuationUnit prettyTyparInst, resL + | RecdAllFieldsCtor _ -> + let prettyTyparInst, _ = PrettyTypes.PrettifyInst amap.g typarInst + prettyTyparInst, layoutMethInfoCSharpStyle amap m denv methInfo methInfo.FormalMethodInst | FSMeth(_, _, vref, _) -> let prettyTyparInst, resL = PrintTastMemberOrVals.prettyLayoutOfValOrMember { denv with showMemberContainers=true } infoReader typarInst vref prettyTyparInst, resL diff --git a/src/Compiler/Checking/OverloadResolutionCache.fs b/src/Compiler/Checking/OverloadResolutionCache.fs index aae0a99bc2f..0c2d3ebeb22 100644 --- a/src/Compiler/Checking/OverloadResolutionCache.fs +++ b/src/Compiler/Checking/OverloadResolutionCache.fs @@ -97,6 +97,7 @@ let rec computeMethInfoHash (minfo: MethInfo) : int = | FSMeth(_, _, vref, _) -> HashingPrimitives.combineHash (hash vref.Stamp) (hash vref.LogicalName) | ILMeth(_, ilMethInfo, _) -> HashingPrimitives.combineHash (hash ilMethInfo.ILName) (hash ilMethInfo.DeclaringTyconRef.Stamp) | DefaultStructCtor(_, _) -> hash "DefaultStructCtor" + | RecdAllFieldsCtor(_, _) -> hash "RecdAllFieldsCtor" | MethInfoWithModifiedReturnType(original, _) -> computeMethInfoHash original #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mb, _, _) -> diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 1a38e10f42b..23f6904e6b5 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -674,6 +674,10 @@ type MethInfo = /// Describes a use of a pseudo-method corresponding to the default constructor for a .NET struct type | DefaultStructCtor of tcGlobals: TcGlobals * structTy: TType + /// Describes a use of the compiler-synthesized all-fields constructor of an F# record type, + /// i.e. the constructor C# sees as `new MyRecord(field1, field2, ...)`. + | RecdAllFieldsCtor of tcGlobals: TcGlobals * recdTy: TType + #if !NO_TYPEPROVIDERS /// Describes a use of a method backed by provided metadata | ProvidedMeth of amap: ImportMap * methodBase: Tainted * extensionMethodPriority: ExtensionMethodPriority option * m: range @@ -689,6 +693,7 @@ type MethInfo = | FSMeth(_, ty, _, _) -> ty | MethInfoWithModifiedReturnType(mi, _) -> mi.ApparentEnclosingType | DefaultStructCtor(_, ty) -> ty + | RecdAllFieldsCtor(_, ty) -> ty #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> ImportProvidedType amap m (mi.PApply((fun mi -> nonNull mi.DeclaringType), m)) @@ -726,6 +731,7 @@ type MethInfo = | _ -> Some (mb, staticParams) #endif | DefaultStructCtor _ -> None + | RecdAllFieldsCtor _ -> None /// Get the extension method priority of the method, if it has one. member x.ExtensionMemberPriorityOption = @@ -737,6 +743,7 @@ type MethInfo = #endif | MethInfoWithModifiedReturnType(mi, _) -> mi.ExtensionMemberPriorityOption | DefaultStructCtor _ -> None + | RecdAllFieldsCtor _ -> None /// Get the extension method priority of the method. If it is not an extension method /// then use the highest possible value since non-extension methods always take priority @@ -754,6 +761,7 @@ type MethInfo = | ProvidedMeth(_, mi, _, m) -> "ProvidedMeth: " + mi.PUntaint((fun mi -> mi.Name), m) #endif | DefaultStructCtor _ -> ".ctor" + | RecdAllFieldsCtor _ -> ".ctor" /// Get the method name in LogicalName form, i.e. the name as it would be stored in .NET metadata member x.LogicalName = @@ -765,6 +773,7 @@ type MethInfo = | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.Name), m) #endif | DefaultStructCtor _ -> ".ctor" + | RecdAllFieldsCtor _ -> ".ctor" /// Get the method name in DisplayName form member x.DisplayName = @@ -802,6 +811,7 @@ type MethInfo = | FSMeth(g, _, _, _) -> g | MethInfoWithModifiedReturnType(mi, _) -> mi.TcGlobals | DefaultStructCtor (g, _) -> g + | RecdAllFieldsCtor (g, _) -> g #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, _, _, _) -> amap.g #endif @@ -818,6 +828,7 @@ type MethInfo = memberMethodTypars | MethInfoWithModifiedReturnType(mi, _) -> mi.FormalMethodTypars | DefaultStructCtor _ -> [] + | RecdAllFieldsCtor _ -> [] #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> [] // There will already have been an error if there are generic parameters here. #endif @@ -834,6 +845,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.XmlDoc | MethInfoWithModifiedReturnType(mi, _) -> mi.XmlDoc | DefaultStructCtor _ -> XmlDoc.Empty + | RecdAllFieldsCtor _ -> XmlDoc.Empty #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m)-> let lines = mi.PUntaint((fun mix -> (mix :> IProvidedCustomAttributeProvider).GetXmlDocAttributes(mi.TypeProvider.PUntaintNoFailure id)), m) @@ -856,6 +868,7 @@ type MethInfo = | FSMeth(g, _, vref, _) -> GetArgInfosOfMember x.IsCSharpStyleExtensionMember g vref |> List.map List.length | MethInfoWithModifiedReturnType(mi, _) -> mi.NumArgs | DefaultStructCtor _ -> [0] + | RecdAllFieldsCtor(g, ty) -> [ (tcrefOfAppTy g ty).TrueInstanceFieldsAsList.Length ] #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> [mi.PApplyArray((fun mi -> mi.GetParameters()),"GetParameters", m).Length] // Why is this a list? Answer: because the method might be curried #endif @@ -878,6 +891,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.IsInstanceMember || x.IsCSharpStyleExtensionMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsInstance | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> not mi.IsConstructor && not mi.IsStatic), m) #endif @@ -892,6 +906,7 @@ type MethInfo = | FSMeth _ -> false | MethInfoWithModifiedReturnType(mi, _) -> mi.IsProtectedAccessibility | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsFamily), m) #endif @@ -902,6 +917,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.IsVirtualMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsVirtual | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsVirtual), m) #endif @@ -912,6 +928,7 @@ type MethInfo = | FSMeth(_g, _, vref, _) -> (vref.MemberInfo.Value.MemberFlags.MemberKind = SynMemberKind.Constructor) | MethInfoWithModifiedReturnType(mi, _) -> mi.IsConstructor | DefaultStructCtor _ -> true + | RecdAllFieldsCtor _ -> true #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsConstructor), m) #endif @@ -925,6 +942,7 @@ type MethInfo = | _ -> false | MethInfoWithModifiedReturnType(mi, _) -> mi.IsClassConstructor | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsConstructor && mi.IsStatic), m) // Note: these are never public anyway #endif @@ -935,6 +953,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.MemberInfo.Value.MemberFlags.IsDispatchSlot | MethInfoWithModifiedReturnType(mi, _) -> mi.IsDispatchSlot | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> x.IsVirtual // Note: follow same implementation as ILMeth #endif @@ -947,6 +966,7 @@ type MethInfo = | FSMeth(_g, _, _vref, _) -> false | MethInfoWithModifiedReturnType(mi, _) -> mi.IsFinal | DefaultStructCtor _ -> true + | RecdAllFieldsCtor _ -> true #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsFinal), m) #endif @@ -963,6 +983,7 @@ type MethInfo = | FSMeth(g, _, vref, _) -> isInterfaceTy g minfo.ApparentEnclosingType || vref.IsDispatchSlotMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsAbstract | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsAbstract), m) #endif @@ -976,7 +997,8 @@ type MethInfo = #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsHideBySig), m) // REVIEW: Check this is correct #endif - | DefaultStructCtor _ -> false)) + | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false)) /// Indicates if this is an IL method. member x.IsILMethod = @@ -992,6 +1014,7 @@ type MethInfo = | FSMeth(g, _, vref, _) -> vref.IsFSharpExplicitInterfaceImplementation g | MethInfoWithModifiedReturnType(mi, _) -> mi.IsFSharpExplicitInterfaceImplementation | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> false #endif @@ -1003,6 +1026,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.IsDefiniteFSharpOverrideMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsDefiniteFSharpOverride | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> false #endif @@ -1137,6 +1161,7 @@ type MethInfo = | mi1, MethInfoWithModifiedReturnType(mi2, _) | MethInfoWithModifiedReturnType(mi1, _), mi2 -> MethInfo.MethInfosUseIdenticalDefinitions mi1 mi2 | DefaultStructCtor _, DefaultStructCtor _ -> tyconRefEq x1.TcGlobals x1.DeclaringTyconRef x2.DeclaringTyconRef + | RecdAllFieldsCtor _, RecdAllFieldsCtor _ -> tyconRefEq x1.TcGlobals x1.DeclaringTyconRef x2.DeclaringTyconRef #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi1, _, _), ProvidedMeth(_, mi2, _, _) -> ProvidedMethodBase.TaintedEquals (mi1, mi2) #endif @@ -1150,6 +1175,7 @@ type MethInfo = | MethInfoWithModifiedReturnType(mi,_) -> mi.ComputeHashCode() | DefaultStructCtor(_, _ty) -> 34892 // "ty" doesn't support hashing. We could use "hash (tcrefOfAppTy g ty).CompiledName" or // something but we don't have a "g" parameter here yet. But this hash need only be very approximate anyway + | RecdAllFieldsCtor(_, _ty) -> 34893 // Approximate, as with DefaultStructCtor above. #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, _) -> ProvidedMethodInfo.TaintedGetHashCode mi #endif @@ -1164,6 +1190,7 @@ type MethInfo = | FSMeth(g, ty, vref, pri) -> FSMeth(g, instType inst ty, vref, pri) | MethInfoWithModifiedReturnType(mi, retTy) -> MethInfoWithModifiedReturnType(mi.Instantiate(amap, m, inst), retTy) | DefaultStructCtor(g, ty) -> DefaultStructCtor(g, instType inst ty) + | RecdAllFieldsCtor(g, ty) -> RecdAllFieldsCtor(g, instType inst ty) #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> match inst with @@ -1183,6 +1210,7 @@ type MethInfo = retTy |> Option.map (instType inst) | MethInfoWithModifiedReturnType(_,retTy) -> Some retTy | DefaultStructCtor _ -> None + | RecdAllFieldsCtor _ -> None #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> GetCompiledReturnTyOfProvidedMethodInfo amap m mi @@ -1220,6 +1248,10 @@ type MethInfo = paramTypes |> List.mapSquared (fun (ParamNameAndType(_, ty)) -> instType inst ty) | MethInfoWithModifiedReturnType(mi,_) -> mi.GetParamTypes(amap,m,minst) | DefaultStructCtor _ -> [] + | RecdAllFieldsCtor(g, ty) -> + let tcref = tcrefOfAppTy g ty + let tinst = argsOfAppTy g ty + [ tcref.TrueInstanceFieldsAsList |> List.map (fun fspec -> actualTyOfRecdFieldForTycon tcref.Deref tinst fspec) ] #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> // A single group of tupled arguments @@ -1245,6 +1277,7 @@ type MethInfo = else [] | MethInfoWithModifiedReturnType(mi,_) -> mi.GetObjArgTypes(amap, m, minst) | DefaultStructCtor _ -> [] + | RecdAllFieldsCtor _ -> [] #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> if x.IsInstance then [ ImportProvidedType amap m (mi.PApply((fun mi -> nonNull mi.DeclaringType), m)) ] // find the type of the 'this' argument @@ -1299,6 +1332,9 @@ type MethInfo = | MethInfoWithModifiedReturnType(mi,_) -> mi.GetParamAttribs(amap, m) | DefaultStructCtor _ -> [[]] + | RecdAllFieldsCtor(g, ty) -> + [ (tcrefOfAppTy g ty).TrueInstanceFieldsAsList + |> List.map (fun _ -> ParamAttribs(false, false, false, NotOptional, NoCallerInfo, ReflectedArgInfo.None)) ] #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, _) -> @@ -1341,6 +1377,7 @@ type MethInfo = MakeSlotSig(x.LogicalName, x.ApparentEnclosingType, formalEnclosingTypars, formalMethTypars, formalParams, formalRetTy) | MethInfoWithModifiedReturnType(mi,_) -> mi.GetSlotSig(amap, m) | DefaultStructCtor _ -> error(InternalError("no slotsig for DefaultStructCtor", m)) + | RecdAllFieldsCtor _ -> error(InternalError("no slotsig for RecdAllFieldsCtor", m)) | _ -> let g = x.TcGlobals // slotsigs must contain the formal types for the arguments and return type @@ -1407,6 +1444,11 @@ type MethInfo = | MethInfoWithModifiedReturnType(_mi,_) -> failwith "unreachable" | DefaultStructCtor _ -> [[]] + | RecdAllFieldsCtor(g, ty) -> + let tcref = tcrefOfAppTy g ty + let tinst = argsOfAppTy g ty + [ tcref.TrueInstanceFieldsAsList + |> List.map (fun fspec -> ParamNameAndType(Some (mkSynId m fspec.LogicalName), actualTyOfRecdFieldForTycon tcref.Deref tinst fspec)) ] #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, _) -> // A single set of tupled parameters @@ -1438,6 +1480,7 @@ type MethInfo = | None -> false | MethInfoWithModifiedReturnType _ -> false | DefaultStructCtor _ -> false + | RecdAllFieldsCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> false #endif diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi index e091834e271..44b616bb961 100644 --- a/src/Compiler/Checking/infos.fsi +++ b/src/Compiler/Checking/infos.fsi @@ -320,6 +320,9 @@ type MethInfo = /// Describes a use of a pseudo-method corresponding to the default constructor for a .NET struct type | DefaultStructCtor of tcGlobals: TcGlobals * structTy: TType + /// Describes a use of the compiler-synthesized all-fields constructor of an F# record type + | RecdAllFieldsCtor of tcGlobals: TcGlobals * recdTy: TType + #if !NO_TYPEPROVIDERS /// Describes a use of a method backed by provided metadata | ProvidedMeth of diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index dc27f32bae9..b43b8670b97 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1786,6 +1786,7 @@ featureEmptyBodiedComputationExpressions,"Support for computation expressions wi featureAllowAccessModifiersToAutoPropertiesGettersAndSetters,"Allow access modifiers to auto properties getters and setters" 3871,tcAccessModifiersNotAllowedInSRTPConstraint,"Access modifiers cannot be applied to an SRTP constraint." featureAllowObjectExpressionWithoutOverrides,"Allow object expressions without overrides" +featureRecordConstructorSyntax,"Constructing a record via its all-fields constructor" featureUseTypeSubsumptionCache,"Use type conversion cache during compilation" 3872,tcPartialActivePattern,"Multi-case partial active patterns are not supported. Consider using a single-case partial active pattern or a full active pattern." featureDontWarnOnUppercaseIdentifiersInBindingPatterns,"Don't warn on uppercase identifiers in binding patterns" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index da4ef690311..81a34bc20a4 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -110,6 +110,7 @@ type LanguageFeature = | PreprocessorElif | ExceptionFieldSerializationSupport | ErrorOnMissingSignatureAttribute + | RecordConstructorSyntax /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -263,6 +264,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) LanguageFeature.MethodOverloadsCache, previewVersion // Performance optimization for overload resolution LanguageFeature.ImplicitDIMCoverage, languageVersion110 LanguageFeature.ErrorOnMissingSignatureAttribute, previewVersion // Opt-in: turn FS3888 from warning into error + LanguageFeature.RecordConstructorSyntax, previewVersion // Allow constructing a record via its all-fields constructor, e.g. MyRecord(a, b) ] static let defaultLanguageVersion = LanguageVersion("default") @@ -459,6 +461,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) | LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif () | LanguageFeature.ExceptionFieldSerializationSupport -> FSComp.SR.featureExceptionFieldSerializationSupport () | LanguageFeature.ErrorOnMissingSignatureAttribute -> FSComp.SR.featureErrorOnMissingSignatureAttribute () + | LanguageFeature.RecordConstructorSyntax -> FSComp.SR.featureRecordConstructorSyntax () /// 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 5ba352191af..c5fd73624e4 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -101,6 +101,7 @@ type LanguageFeature = | PreprocessorElif | ExceptionFieldSerializationSupport | ErrorOnMissingSignatureAttribute + | RecordConstructorSyntax /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index 742e7640293..a314d1a6b3f 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -813,6 +813,7 @@ module internal SymbolHelpers = | MethInfoWithModifiedReturnType(mi,_) -> getKeywordForMethInfo mi | DefaultStructCtor _ -> None + | RecdAllFieldsCtor _ -> None #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> None #endif diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 84e5e3cc50c..156abf55093 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -592,6 +592,11 @@ vypsat literály libovolné velikosti + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells informační zprávy související s referenčními buňkami diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index d6edecb4def..470a7b34a58 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -592,6 +592,11 @@ Literale beliebiger Größe auflisten + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells Informationsmeldungen im Zusammenhang mit Bezugszellen diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 46573e801f1..d123670bd56 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -592,6 +592,11 @@ enumerar literales de cualquier tamaño + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells mensajes informativos relacionados con las celdas de referencia diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 1acfc469b5b..39783312ec9 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -592,6 +592,11 @@ répertorier les littéraux de n’importe quelle taille + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells messages d’information liés aux cellules de référence diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index deaca46fad0..b4b25d544d2 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -592,6 +592,11 @@ elenca valori letterali di qualsiasi dimensione + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells messaggi informativi relativi alle celle di riferimento diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 10029fab134..abd61d4c629 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -592,6 +592,11 @@ 任意のサイズのリテラルを一覧表示する + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells 参照セルに関連する情報メッセージ diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 1d7a19af957..b1196eb348a 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -592,6 +592,11 @@ 모든 크기의 목록 리터럴 + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells 참조 셀과 관련된 정보 메시지 diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 170e6e37718..edd3076d78c 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -592,6 +592,11 @@ wyświetlanie na liście literałów o dowolnym rozmiarze + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells komunikaty informacyjne związane z odwołaniami do komórek diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 87ef255a7e8..44bf51a7421 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -592,6 +592,11 @@ literais de lista de qualquer tamanho + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells mensagens informativas relacionadas a células de referência diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 57f29e98517..95e06e0c8b4 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -592,6 +592,11 @@ список литералов любого размера + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells информационные сообщения, связанные с ссылочными ячейками diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 08753e47a27..b727da6b532 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -592,6 +592,11 @@ tüm boyutlardaki sabit değerleri listele + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells başvuru hücreleriyle ilgili bilgi mesajları diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index e117ab0ec5d..b8095b6afd9 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -592,6 +592,11 @@ 列出任何大小的文本 + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells 与引用单元格相关的信息性消息 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 31c5596a098..c2322b4bc0b 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -592,6 +592,11 @@ 列出任何大小的常值 + + Constructing a record via its all-fields constructor + Constructing a record via its all-fields constructor + + informational messages related to reference cells 與參考儲存格相關的資訊訊息 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs index 3bae9db5802..b265b830dcd 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs @@ -616,3 +616,117 @@ module RecordTypes = |> typecheck |> shouldFail |> withSingleDiagnostic (Error 954, Line 4, Col 18, Line 4, Col 30, "This type definition involves an immediate cyclic reference through a struct field or inheritance relation") + + // Feature: allow constructing an F# record by calling its (synthesized) all-fields + // constructor positionally, e.g. MyRecord(1, "a"), as is already possible from C#. + // These tests describe the target behaviour and currently FAIL (records expose no + // F#-callable constructor; only { Field = ... } record syntax is permitted). + + [] + let ``Record can be constructed positionally via its all-fields constructor`` () = + Fsx """ +type Person = { Name : string; Age : int } +let p = Person("Isaac", 21) +if p.Name <> "Isaac" then failwith "wrong Name" +if p.Age <> 21 then failwith "wrong Age" +if p <> { Name = "Isaac"; Age = 21 } then failwith "not equal to record-syntax value" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + [] + let ``Record positional constructor evaluates arguments left-to-right`` () = + Fsx """ +type R = { A : int; B : int } +let log = System.Collections.Generic.List() +let side n = log.Add n; n +let r = R(side 1, side 2) +if List.ofSeq log <> [1; 2] then failwith "arguments not evaluated left-to-right" +if r.A <> 1 || r.B <> 2 then failwith "wrong field values" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + [] + let ``Record constructor supports named arguments matching field names`` () = + Fsx """ +type Person = { Name : string; Age : int } +let p = Person(Age = 21, Name = "Isaac") +if p.Name <> "Isaac" || p.Age <> 21 then failwith "wrong field values" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + [] + let ``Generic record can be constructed positionally`` () = + Fsx """ +type Boxed<'T> = { Value : 'T; Label : string } +let b = Boxed(42, "answer") +if b.Value <> 42 || b.Label <> "answer" then failwith "wrong field values" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + [] + let ``Struct record can be constructed positionally with all fields`` () = + Fsx """ +[] +type Point = { X : int; Y : int } +let pt = Point(3, 4) +if pt.X <> 3 || pt.Y <> 4 then failwith "wrong field values" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + // FS-1073 scope precedence / backward compatibility: the type name is treated as a record + // constructor ONLY when there is no other binding with the same name in scope. Here a value + // binding 'Record' shadows the record type's synthesized constructor, so 'Record 0' must remain + // the function application (returning a string), NOT a record construction. Calling 'string' on + // it therefore yields the function's own result. + [] + let ``Record constructor does not shadow an in-scope value binding of the same name`` () = + Fsx """ +let Record (x: int) : string = "function" // value binding 'Record : int -> string' +type Record = { N : int } // record whose all-fields ctor would be 'Record : int -> Record' +let result : string = string (Record 0) // 'Record 0' must be the function application, not the ctor +if result <> "function" then failwith $"expected the in-scope function to be called, got '{result}'" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + // FS-1073 accessibility gating: the synthesized constructor must be no more accessible than '{ ... }' + // construction. A record with 'private' representation can be constructed via the ctor only where the + // representation is accessible (i.e. inside the declaring module), never from outside - so F# does not + // inherit the C# behaviour where the IL constructor is public regardless of the record's accessibility. + [] + let ``Private record can be constructed via its constructor inside the declaring scope`` () = + Fsx """ +type R = private { A : int; B : int } +let r = R(1, 2) +if r.A <> 1 || r.B <> 2 then failwith "wrong field values" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + + [] + let ``Private record constructor is not accessible from outside the declaring module`` () = + FSharp """ +namespace Test + +module M = + type R = private { A : int; B : int } + +module N = + let bad = M.R(1, 2) + """ + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withSingleDiagnostic (Error 801, Line 8, Col 15, Line 8, Col 18, "This type has no accessible object constructors") From d3e180459b83e4e96ae37d5b7cf35220072ad315 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Fri, 19 Jun 2026 11:58:05 +0100 Subject: [PATCH 02/10] Rename MethInfo.RecdAllFieldsCtor to RecdCtor Shorter name; a record has exactly one synthesized constructor, so the 'AllFields' qualifier is not needed to disambiguate. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Compiler/Checking/AccessibilityLogic.fs | 2 +- src/Compiler/Checking/AttributeChecking.fs | 6 +- src/Compiler/Checking/ConstraintSolver.fs | 2 +- src/Compiler/Checking/InfoReader.fs | 4 +- src/Compiler/Checking/MethodCalls.fs | 4 +- src/Compiler/Checking/NameResolution.fs | 2 +- src/Compiler/Checking/NicePrint.fs | 2 +- .../Checking/OverloadResolutionCache.fs | 2 +- src/Compiler/Checking/infos.fs | 62 +++++++++---------- src/Compiler/Checking/infos.fsi | 2 +- src/Compiler/Symbols/SymbolHelpers.fs | 2 +- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Compiler/Checking/AccessibilityLogic.fs b/src/Compiler/Checking/AccessibilityLogic.fs index 7b3ba81f5ff..19876c039b0 100644 --- a/src/Compiler/Checking/AccessibilityLogic.fs +++ b/src/Compiler/Checking/AccessibilityLogic.fs @@ -356,7 +356,7 @@ let rec IsTypeAndMethInfoAccessible amap m accessDomainTy ad = function | FSMeth (_, _, vref, _) -> IsValAccessible ad vref | MethInfoWithModifiedReturnType(mi,_) -> IsTypeAndMethInfoAccessible amap m accessDomainTy ad mi | DefaultStructCtor(g, ty) -> IsTypeAccessible g amap m ad ty - | RecdAllFieldsCtor(g, ty) -> + | RecdCtor(g, ty) -> // The synthesized all-fields constructor must be no more accessible than constructing the record // with '{ ... }' syntax: require the type, its representation and every field to be accessible. // This stops F# inheriting the C# behaviour where the IL constructor is public regardless of the diff --git a/src/Compiler/Checking/AttributeChecking.fs b/src/Compiler/Checking/AttributeChecking.fs index c7ae9643533..213e09a917d 100755 --- a/src/Compiler/Checking/AttributeChecking.fs +++ b/src/Compiler/Checking/AttributeChecking.fs @@ -155,7 +155,7 @@ let rec GetAttribInfosOfMethod amap m minfo = | FSMeth (g, _, vref, _) -> vref.Attribs |> AttribInfosOfFS g | MethInfoWithModifiedReturnType(mi,_) -> GetAttribInfosOfMethod amap m mi | DefaultStructCtor _ -> [] - | RecdAllFieldsCtor _ -> [] + | RecdCtor _ -> [] #if !NO_TYPEPROVIDERS // TODO: provided attributes | ProvidedMeth (_, _mi, _, _m) -> @@ -192,7 +192,7 @@ let rec BindMethInfoAttributes m minfo f1 f2 f3 = | FSMeth (_, _, vref, _) -> f2 vref.Attribs | MethInfoWithModifiedReturnType(mi,_) -> BindMethInfoAttributes m mi f1 f2 f3 | DefaultStructCtor _ -> f2 [] - | RecdAllFieldsCtor _ -> f2 [] + | RecdCtor _ -> f2 [] #if !NO_TYPEPROVIDERS | ProvidedMeth (_, mi, _, _) -> f3 (mi.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) #endif @@ -248,7 +248,7 @@ let rec MethInfoHasWellKnownAttribute g (m: range) (ilFlag: WellKnownILAttribute | ILMeth(_, ilMethInfo, _) -> ilMethInfo.RawMetadata.HasWellKnownAttribute(g, ilFlag) | FSMeth(_, _, vref, _) -> ValHasWellKnownAttribute g valFlag vref.Deref | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false | MethInfoWithModifiedReturnType(mi, _) -> MethInfoHasWellKnownAttribute g m ilFlag valFlag attribSpec mi #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> MethInfoHasAttribute g m attribSpec minfo diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index bb47931fe3d..1315a39163f 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -2188,7 +2188,7 @@ and MemberConstraintSolutionOfMethInfo css m minfo minst staticTyOpt = | MethInfo.DefaultStructCtor _ -> error(InternalError("the default struct constructor was the unexpected solution to a trait constraint", m)) - | MethInfo.RecdAllFieldsCtor _ -> + | MethInfo.RecdCtor _ -> error(InternalError("the record all-fields constructor was the unexpected solution to a trait constraint", m)) #if !NO_TYPEPROVIDERS diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index ae14bc640cf..85fb98316dd 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -966,7 +966,7 @@ type InfoReader(g: TcGlobals, amap: ImportMap) as this = // callable from C# as 'new MyRecord(f1, f2, ...)'. Under the RecordConstructorSyntax feature we // surface that same constructor to F# too (it elaborates to a record allocation - see BuildMethodCall). if g.langVersion.SupportsFeature LanguageFeature.RecordConstructorSyntax && tcref.IsRecordTycon then - declaredCtors @ [ RecdAllFieldsCtor(g, origTy) ] + declaredCtors @ [ RecdCtor(g, origTy) ] else declaredCtors ) @@ -1272,7 +1272,7 @@ let rec GetXmlDocSigOfMethInfo (infoReader: InfoReader) m (minfo: MethInfo) = | ValueSome tcref -> Some(None, $"M:{tcref.CompiledRepresentationForNamedType.FullName}.#ctor") | _ -> None - | RecdAllFieldsCtor(g, ty) -> + | RecdCtor(g, ty) -> match tryTcrefOfAppTy g ty with | ValueSome tcref -> Some(None, $"M:{tcref.CompiledRepresentationForNamedType.FullName}.#ctor") diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index 3a78b011543..d5f7510cd66 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -1123,7 +1123,7 @@ let rec MakeMethInfoCall (amap: ImportMap) m (minfo: MethInfo) minst args static | DefaultStructCtor(_, ty) -> mkDefault (m, ty) - | RecdAllFieldsCtor(g, ty) -> + | RecdCtor(g, ty) -> let tcref = tcrefOfAppTy g ty let tinst = argsOfAppTy g ty mkRecordExpr g (RecdExpr, tcref, tinst, tcref.TrueInstanceFieldsAsRefList, args, m) @@ -1274,7 +1274,7 @@ let rec BuildMethodCall tcVal g amap isMutable m isProp minfo valUseFlags minst mkDefault (m, ty), ty // Build a record allocation from a call to the synthesized all-fields constructor of an F# record. - | RecdAllFieldsCtor (g, ty) -> + | RecdCtor (g, ty) -> let tcref = tcrefOfAppTy g ty let tinst = argsOfAppTy g ty mkRecordExpr g (RecdExpr, tcref, tinst, tcref.TrueInstanceFieldsAsRefList, allArgs, m), ty) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 69b3d1d8b98..81fa1a5eb2a 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -706,7 +706,7 @@ let rec TrySelectExtensionMethInfoOfILExtMem m amap apparentTy (actualParent, mi #endif | DefaultStructCtor _ -> None - | RecdAllFieldsCtor _ -> + | RecdCtor _ -> None /// Select from a list of extension methods diff --git a/src/Compiler/Checking/NicePrint.fs b/src/Compiler/Checking/NicePrint.fs index 62dcc97a7b5..eb55bd1dc10 100644 --- a/src/Compiler/Checking/NicePrint.fs +++ b/src/Compiler/Checking/NicePrint.fs @@ -1789,7 +1789,7 @@ module InfoMemberPrinting = let prettyTyparInst, _ = PrettyTypes.PrettifyInst amap.g typarInst let resL = PrintTypes.layoutTyconRef denv methInfo.ApparentEnclosingTyconRef ^^ wordL punctuationUnit prettyTyparInst, resL - | RecdAllFieldsCtor _ -> + | RecdCtor _ -> let prettyTyparInst, _ = PrettyTypes.PrettifyInst amap.g typarInst prettyTyparInst, layoutMethInfoCSharpStyle amap m denv methInfo methInfo.FormalMethodInst | FSMeth(_, _, vref, _) -> diff --git a/src/Compiler/Checking/OverloadResolutionCache.fs b/src/Compiler/Checking/OverloadResolutionCache.fs index 0c2d3ebeb22..06a2252076b 100644 --- a/src/Compiler/Checking/OverloadResolutionCache.fs +++ b/src/Compiler/Checking/OverloadResolutionCache.fs @@ -97,7 +97,7 @@ let rec computeMethInfoHash (minfo: MethInfo) : int = | FSMeth(_, _, vref, _) -> HashingPrimitives.combineHash (hash vref.Stamp) (hash vref.LogicalName) | ILMeth(_, ilMethInfo, _) -> HashingPrimitives.combineHash (hash ilMethInfo.ILName) (hash ilMethInfo.DeclaringTyconRef.Stamp) | DefaultStructCtor(_, _) -> hash "DefaultStructCtor" - | RecdAllFieldsCtor(_, _) -> hash "RecdAllFieldsCtor" + | RecdCtor(_, _) -> hash "RecdCtor" | MethInfoWithModifiedReturnType(original, _) -> computeMethInfoHash original #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mb, _, _) -> diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 23f6904e6b5..692ec248af0 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -676,7 +676,7 @@ type MethInfo = /// Describes a use of the compiler-synthesized all-fields constructor of an F# record type, /// i.e. the constructor C# sees as `new MyRecord(field1, field2, ...)`. - | RecdAllFieldsCtor of tcGlobals: TcGlobals * recdTy: TType + | RecdCtor of tcGlobals: TcGlobals * recdTy: TType #if !NO_TYPEPROVIDERS /// Describes a use of a method backed by provided metadata @@ -693,7 +693,7 @@ type MethInfo = | FSMeth(_, ty, _, _) -> ty | MethInfoWithModifiedReturnType(mi, _) -> mi.ApparentEnclosingType | DefaultStructCtor(_, ty) -> ty - | RecdAllFieldsCtor(_, ty) -> ty + | RecdCtor(_, ty) -> ty #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> ImportProvidedType amap m (mi.PApply((fun mi -> nonNull mi.DeclaringType), m)) @@ -731,7 +731,7 @@ type MethInfo = | _ -> Some (mb, staticParams) #endif | DefaultStructCtor _ -> None - | RecdAllFieldsCtor _ -> None + | RecdCtor _ -> None /// Get the extension method priority of the method, if it has one. member x.ExtensionMemberPriorityOption = @@ -743,7 +743,7 @@ type MethInfo = #endif | MethInfoWithModifiedReturnType(mi, _) -> mi.ExtensionMemberPriorityOption | DefaultStructCtor _ -> None - | RecdAllFieldsCtor _ -> None + | RecdCtor _ -> None /// Get the extension method priority of the method. If it is not an extension method /// then use the highest possible value since non-extension methods always take priority @@ -761,7 +761,7 @@ type MethInfo = | ProvidedMeth(_, mi, _, m) -> "ProvidedMeth: " + mi.PUntaint((fun mi -> mi.Name), m) #endif | DefaultStructCtor _ -> ".ctor" - | RecdAllFieldsCtor _ -> ".ctor" + | RecdCtor _ -> ".ctor" /// Get the method name in LogicalName form, i.e. the name as it would be stored in .NET metadata member x.LogicalName = @@ -773,7 +773,7 @@ type MethInfo = | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.Name), m) #endif | DefaultStructCtor _ -> ".ctor" - | RecdAllFieldsCtor _ -> ".ctor" + | RecdCtor _ -> ".ctor" /// Get the method name in DisplayName form member x.DisplayName = @@ -811,7 +811,7 @@ type MethInfo = | FSMeth(g, _, _, _) -> g | MethInfoWithModifiedReturnType(mi, _) -> mi.TcGlobals | DefaultStructCtor (g, _) -> g - | RecdAllFieldsCtor (g, _) -> g + | RecdCtor (g, _) -> g #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, _, _, _) -> amap.g #endif @@ -828,7 +828,7 @@ type MethInfo = memberMethodTypars | MethInfoWithModifiedReturnType(mi, _) -> mi.FormalMethodTypars | DefaultStructCtor _ -> [] - | RecdAllFieldsCtor _ -> [] + | RecdCtor _ -> [] #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> [] // There will already have been an error if there are generic parameters here. #endif @@ -845,7 +845,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.XmlDoc | MethInfoWithModifiedReturnType(mi, _) -> mi.XmlDoc | DefaultStructCtor _ -> XmlDoc.Empty - | RecdAllFieldsCtor _ -> XmlDoc.Empty + | RecdCtor _ -> XmlDoc.Empty #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m)-> let lines = mi.PUntaint((fun mix -> (mix :> IProvidedCustomAttributeProvider).GetXmlDocAttributes(mi.TypeProvider.PUntaintNoFailure id)), m) @@ -868,7 +868,7 @@ type MethInfo = | FSMeth(g, _, vref, _) -> GetArgInfosOfMember x.IsCSharpStyleExtensionMember g vref |> List.map List.length | MethInfoWithModifiedReturnType(mi, _) -> mi.NumArgs | DefaultStructCtor _ -> [0] - | RecdAllFieldsCtor(g, ty) -> [ (tcrefOfAppTy g ty).TrueInstanceFieldsAsList.Length ] + | RecdCtor(g, ty) -> [ (tcrefOfAppTy g ty).TrueInstanceFieldsAsList.Length ] #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> [mi.PApplyArray((fun mi -> mi.GetParameters()),"GetParameters", m).Length] // Why is this a list? Answer: because the method might be curried #endif @@ -891,7 +891,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.IsInstanceMember || x.IsCSharpStyleExtensionMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsInstance | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> not mi.IsConstructor && not mi.IsStatic), m) #endif @@ -906,7 +906,7 @@ type MethInfo = | FSMeth _ -> false | MethInfoWithModifiedReturnType(mi, _) -> mi.IsProtectedAccessibility | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsFamily), m) #endif @@ -917,7 +917,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.IsVirtualMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsVirtual | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsVirtual), m) #endif @@ -928,7 +928,7 @@ type MethInfo = | FSMeth(_g, _, vref, _) -> (vref.MemberInfo.Value.MemberFlags.MemberKind = SynMemberKind.Constructor) | MethInfoWithModifiedReturnType(mi, _) -> mi.IsConstructor | DefaultStructCtor _ -> true - | RecdAllFieldsCtor _ -> true + | RecdCtor _ -> true #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsConstructor), m) #endif @@ -942,7 +942,7 @@ type MethInfo = | _ -> false | MethInfoWithModifiedReturnType(mi, _) -> mi.IsClassConstructor | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsConstructor && mi.IsStatic), m) // Note: these are never public anyway #endif @@ -953,7 +953,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.MemberInfo.Value.MemberFlags.IsDispatchSlot | MethInfoWithModifiedReturnType(mi, _) -> mi.IsDispatchSlot | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> x.IsVirtual // Note: follow same implementation as ILMeth #endif @@ -966,7 +966,7 @@ type MethInfo = | FSMeth(_g, _, _vref, _) -> false | MethInfoWithModifiedReturnType(mi, _) -> mi.IsFinal | DefaultStructCtor _ -> true - | RecdAllFieldsCtor _ -> true + | RecdCtor _ -> true #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsFinal), m) #endif @@ -983,7 +983,7 @@ type MethInfo = | FSMeth(g, _, vref, _) -> isInterfaceTy g minfo.ApparentEnclosingType || vref.IsDispatchSlotMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsAbstract | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsAbstract), m) #endif @@ -998,7 +998,7 @@ type MethInfo = | ProvidedMeth(_, mi, _, m) -> mi.PUntaint((fun mi -> mi.IsHideBySig), m) // REVIEW: Check this is correct #endif | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false)) + | RecdCtor _ -> false)) /// Indicates if this is an IL method. member x.IsILMethod = @@ -1014,7 +1014,7 @@ type MethInfo = | FSMeth(g, _, vref, _) -> vref.IsFSharpExplicitInterfaceImplementation g | MethInfoWithModifiedReturnType(mi, _) -> mi.IsFSharpExplicitInterfaceImplementation | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> false #endif @@ -1026,7 +1026,7 @@ type MethInfo = | FSMeth(_, _, vref, _) -> vref.IsDefiniteFSharpOverrideMember | MethInfoWithModifiedReturnType(mi, _) -> mi.IsDefiniteFSharpOverride | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> false #endif @@ -1161,7 +1161,7 @@ type MethInfo = | mi1, MethInfoWithModifiedReturnType(mi2, _) | MethInfoWithModifiedReturnType(mi1, _), mi2 -> MethInfo.MethInfosUseIdenticalDefinitions mi1 mi2 | DefaultStructCtor _, DefaultStructCtor _ -> tyconRefEq x1.TcGlobals x1.DeclaringTyconRef x2.DeclaringTyconRef - | RecdAllFieldsCtor _, RecdAllFieldsCtor _ -> tyconRefEq x1.TcGlobals x1.DeclaringTyconRef x2.DeclaringTyconRef + | RecdCtor _, RecdCtor _ -> tyconRefEq x1.TcGlobals x1.DeclaringTyconRef x2.DeclaringTyconRef #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi1, _, _), ProvidedMeth(_, mi2, _, _) -> ProvidedMethodBase.TaintedEquals (mi1, mi2) #endif @@ -1175,7 +1175,7 @@ type MethInfo = | MethInfoWithModifiedReturnType(mi,_) -> mi.ComputeHashCode() | DefaultStructCtor(_, _ty) -> 34892 // "ty" doesn't support hashing. We could use "hash (tcrefOfAppTy g ty).CompiledName" or // something but we don't have a "g" parameter here yet. But this hash need only be very approximate anyway - | RecdAllFieldsCtor(_, _ty) -> 34893 // Approximate, as with DefaultStructCtor above. + | RecdCtor(_, _ty) -> 34893 // Approximate, as with DefaultStructCtor above. #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, _) -> ProvidedMethodInfo.TaintedGetHashCode mi #endif @@ -1190,7 +1190,7 @@ type MethInfo = | FSMeth(g, ty, vref, pri) -> FSMeth(g, instType inst ty, vref, pri) | MethInfoWithModifiedReturnType(mi, retTy) -> MethInfoWithModifiedReturnType(mi.Instantiate(amap, m, inst), retTy) | DefaultStructCtor(g, ty) -> DefaultStructCtor(g, instType inst ty) - | RecdAllFieldsCtor(g, ty) -> RecdAllFieldsCtor(g, instType inst ty) + | RecdCtor(g, ty) -> RecdCtor(g, instType inst ty) #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> match inst with @@ -1210,7 +1210,7 @@ type MethInfo = retTy |> Option.map (instType inst) | MethInfoWithModifiedReturnType(_,retTy) -> Some retTy | DefaultStructCtor _ -> None - | RecdAllFieldsCtor _ -> None + | RecdCtor _ -> None #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> GetCompiledReturnTyOfProvidedMethodInfo amap m mi @@ -1248,7 +1248,7 @@ type MethInfo = paramTypes |> List.mapSquared (fun (ParamNameAndType(_, ty)) -> instType inst ty) | MethInfoWithModifiedReturnType(mi,_) -> mi.GetParamTypes(amap,m,minst) | DefaultStructCtor _ -> [] - | RecdAllFieldsCtor(g, ty) -> + | RecdCtor(g, ty) -> let tcref = tcrefOfAppTy g ty let tinst = argsOfAppTy g ty [ tcref.TrueInstanceFieldsAsList |> List.map (fun fspec -> actualTyOfRecdFieldForTycon tcref.Deref tinst fspec) ] @@ -1277,7 +1277,7 @@ type MethInfo = else [] | MethInfoWithModifiedReturnType(mi,_) -> mi.GetObjArgTypes(amap, m, minst) | DefaultStructCtor _ -> [] - | RecdAllFieldsCtor _ -> [] + | RecdCtor _ -> [] #if !NO_TYPEPROVIDERS | ProvidedMeth(amap, mi, _, m) -> if x.IsInstance then [ ImportProvidedType amap m (mi.PApply((fun mi -> nonNull mi.DeclaringType), m)) ] // find the type of the 'this' argument @@ -1332,7 +1332,7 @@ type MethInfo = | MethInfoWithModifiedReturnType(mi,_) -> mi.GetParamAttribs(amap, m) | DefaultStructCtor _ -> [[]] - | RecdAllFieldsCtor(g, ty) -> + | RecdCtor(g, ty) -> [ (tcrefOfAppTy g ty).TrueInstanceFieldsAsList |> List.map (fun _ -> ParamAttribs(false, false, false, NotOptional, NoCallerInfo, ReflectedArgInfo.None)) ] @@ -1377,7 +1377,7 @@ type MethInfo = MakeSlotSig(x.LogicalName, x.ApparentEnclosingType, formalEnclosingTypars, formalMethTypars, formalParams, formalRetTy) | MethInfoWithModifiedReturnType(mi,_) -> mi.GetSlotSig(amap, m) | DefaultStructCtor _ -> error(InternalError("no slotsig for DefaultStructCtor", m)) - | RecdAllFieldsCtor _ -> error(InternalError("no slotsig for RecdAllFieldsCtor", m)) + | RecdCtor _ -> error(InternalError("no slotsig for RecdCtor", m)) | _ -> let g = x.TcGlobals // slotsigs must contain the formal types for the arguments and return type @@ -1444,7 +1444,7 @@ type MethInfo = | MethInfoWithModifiedReturnType(_mi,_) -> failwith "unreachable" | DefaultStructCtor _ -> [[]] - | RecdAllFieldsCtor(g, ty) -> + | RecdCtor(g, ty) -> let tcref = tcrefOfAppTy g ty let tinst = argsOfAppTy g ty [ tcref.TrueInstanceFieldsAsList @@ -1480,7 +1480,7 @@ type MethInfo = | None -> false | MethInfoWithModifiedReturnType _ -> false | DefaultStructCtor _ -> false - | RecdAllFieldsCtor _ -> false + | RecdCtor _ -> false #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> false #endif diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi index 44b616bb961..b7239d9b2b6 100644 --- a/src/Compiler/Checking/infos.fsi +++ b/src/Compiler/Checking/infos.fsi @@ -321,7 +321,7 @@ type MethInfo = | DefaultStructCtor of tcGlobals: TcGlobals * structTy: TType /// Describes a use of the compiler-synthesized all-fields constructor of an F# record type - | RecdAllFieldsCtor of tcGlobals: TcGlobals * recdTy: TType + | RecdCtor of tcGlobals: TcGlobals * recdTy: TType #if !NO_TYPEPROVIDERS /// Describes a use of a method backed by provided metadata diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index a314d1a6b3f..f30665b8231 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -813,7 +813,7 @@ module internal SymbolHelpers = | MethInfoWithModifiedReturnType(mi,_) -> getKeywordForMethInfo mi | DefaultStructCtor _ -> None - | RecdAllFieldsCtor _ -> None + | RecdCtor _ -> None #if !NO_TYPEPROVIDERS | ProvidedMeth _ -> None #endif From 402baa2b8efde75327301c096040445feb5adbe7 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Fri, 19 Jun 2026 12:01:07 +0100 Subject: [PATCH 03/10] Add release notes for record constructors (FS-1073) Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + docs/release-notes/.Language/preview.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 5d4bd0fe6ac..46cc2aef86d 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -95,6 +95,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)) +* Surface the synthesized all-fields constructor of F# record types to F# code under the `RecordConstructorSyntax` preview feature, via a new `MethInfo.RecdCtor` case. ([PR #19974](https://github.com/dotnet/fsharp/pull/19974)) ### Changed diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index 90c1aa2faa6..b094068d7e6 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -3,6 +3,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)) * Added `ErrorOnMissingSignatureAttribute` preview language feature: makes FS3888 (compiler-semantic attribute on the `.fs` but not on the `.fsi`) an error instead of a warning. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) +* Allow constructing a record via its all-fields constructor, e.g. `MyRecord(a, b)`, with positional or named arguments (`RecordConstructorSyntax` preview feature). Accessibility matches `{ ... }` construction. ([Suggestion #722](https://github.com/fsharp/fslang-suggestions/issues/722), [RFC FS-1073](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1073-record-constructors.md), [PR #19974](https://github.com/dotnet/fsharp/pull/19974)) ### Fixed From 8c3997ebb8eff62ea4a77efc26cee6e993407d58 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Mon, 22 Jun 2026 10:36:47 +0100 Subject: [PATCH 04/10] Test that struct/CLIMutable records expose no parameterless constructor The feature surfaces only the all-fields constructor. A struct record's zero-init default and a [] record's IL parameterless .ctor must remain non-callable from F#; both 'Point()' and 'R()' are rejected (FS0501). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Types/RecordTypes/RecordTypes.fs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs index b265b830dcd..5a5c02f04f8 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs @@ -730,3 +730,30 @@ module N = |> typecheck |> shouldFail |> withSingleDiagnostic (Error 801, Line 8, Col 15, Line 8, Col 18, "This type has no accessible object constructors") + + // The feature exposes ONLY the all-fields constructor, never a parameterless one. A struct record + // has a zero-init default and a [] record emits a parameterless .ctor in IL, but neither + // is made callable from F# - 'Point()' / 'R()' must still be rejected (only the all-fields ctor exists). + [] + let ``Struct record does not expose a parameterless constructor`` () = + Fsx """ +[] +type Point = { X : int; Y : int } +let p = Point() + """ + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withSingleDiagnostic (Error 501, Line 4, Col 9, Line 4, Col 16, "The object constructor 'Point' takes 2 argument(s) but is here given 0. The required signature is 'Point(X: int, Y: int) : Point'.") + + [] + let ``CLIMutable record does not expose a parameterless constructor`` () = + Fsx """ +[] +type R = { A : int; B : int } +let r = R() + """ + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withSingleDiagnostic (Error 501, Line 4, Col 9, Line 4, Col 12, "The object constructor 'R' takes 2 argument(s) but is here given 0. The required signature is 'R(A: int, B: int) : R'.") From f56706b37ef3d4711f9a4f58339626d9be76fbf8 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Mon, 22 Jun 2026 10:46:38 +0100 Subject: [PATCH 05/10] Clarify wording: struct = default initialization, CLIMutable = parameterless ctor A struct record's 'Point()' is default (zero) initialization, not a real constructor; only [] emits an actual parameterless .ctor. Name the two tests accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Conformance/Types/RecordTypes/RecordTypes.fs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs index 5a5c02f04f8..bba318ac0bc 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs @@ -731,11 +731,10 @@ module N = |> shouldFail |> withSingleDiagnostic (Error 801, Line 8, Col 15, Line 8, Col 18, "This type has no accessible object constructors") - // The feature exposes ONLY the all-fields constructor, never a parameterless one. A struct record - // has a zero-init default and a [] record emits a parameterless .ctor in IL, but neither - // is made callable from F# - 'Point()' / 'R()' must still be rejected (only the all-fields ctor exists). + // Only the all-fields constructor is exposed: a struct record's default (zero) initialization and a + // [] record's IL parameterless .ctor both stay unavailable from F#. [] - let ``Struct record does not expose a parameterless constructor`` () = + let ``Struct record does not expose parameterless default initialization`` () = Fsx """ [] type Point = { X : int; Y : int } @@ -747,7 +746,7 @@ let p = Point() |> withSingleDiagnostic (Error 501, Line 4, Col 9, Line 4, Col 16, "The object constructor 'Point' takes 2 argument(s) but is here given 0. The required signature is 'Point(X: int, Y: int) : Point'.") [] - let ``CLIMutable record does not expose a parameterless constructor`` () = + let ``CLIMutable record does not expose its parameterless constructor`` () = Fsx """ [] type R = { A : int; B : int } From 83db34dab0717956a3239d60887ed7e157d79856 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Mon, 22 Jun 2026 11:58:35 +0100 Subject: [PATCH 06/10] Fix GoToDefinition for record constructor calls + IDE smoke tests rangeOfMethInfo had no RecdCtor case, so it fell through to ArbitraryValRef (None) and GoToDefinition on a positional record-constructor call navigated nowhere. Add a RecdCtor arm returning the record type's range, mirroring DefaultStructCtor. Adds FSharp.Compiler.Service.Tests smoke tests: go-to-definition lands on the record type, the tooltip mentions the type, and find-all-references links the constructor call to the type declaration. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Compiler/Symbols/SymbolHelpers.fs | 1 + .../FSharp.Compiler.Service.Tests.fsproj | 1 + .../RecordConstructorTests.fs | 56 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 tests/FSharp.Compiler.Service.Tests/RecordConstructorTests.fs diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index f30665b8231..288e3d54f6e 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -65,6 +65,7 @@ module internal SymbolHelpers = | ProvidedMeth(_, mi, _, _) -> Construct.ComputeDefinitionLocationOfProvidedItem mi #endif | DefaultStructCtor(_, AppTy g (tcref, _)) -> Some(rangeOfEntityRef preferFlag tcref) + | RecdCtor(_, AppTy g (tcref, _)) -> Some(rangeOfEntityRef preferFlag tcref) | _ -> minfo.ArbitraryValRef |> Option.map (rangeOfValRef preferFlag) let rangeOfEventInfo preferFlag (einfo: EventInfo) = diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index 1cbea95b31e..ccc88fd0bcf 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -34,6 +34,7 @@ + diff --git a/tests/FSharp.Compiler.Service.Tests/RecordConstructorTests.fs b/tests/FSharp.Compiler.Service.Tests/RecordConstructorTests.fs new file mode 100644 index 00000000000..625a49e3e23 --- /dev/null +++ b/tests/FSharp.Compiler.Service.Tests/RecordConstructorTests.fs @@ -0,0 +1,56 @@ +module FSharp.Compiler.Service.Tests.RecordConstructorTests + +open FSharp.Compiler.EditorServices +open FSharp.Compiler.Service.Tests.Common +open FSharp.Compiler.Text +open Xunit + +// IDE smoke tests for the RecordConstructorSyntax feature (FS-1073): a positional record-constructor +// call should behave like any other constructor for tooling - go-to-definition lands on the record type, +// the tooltip shows the type, and the use is linked back to the type declaration. + +let private source = """ +module M +type MyRecord = { A: int; B: int } +let r = MyRecord(1, 2) +""" + +let private tooltipToString (ToolTipText items) = + items + |> List.collect (function + | ToolTipElement.Group elements -> elements |> List.collect (fun e -> List.ofArray e.MainDescription) + | _ -> []) + |> List.map (fun t -> t.Text) + |> String.concat "" + +[] +let ``GoToDefinition on a record constructor call navigates to the record type`` () = + let _, checkResults = parseAndCheckScriptPreview("Test.fsx", source) + // 'MyRecord' constructor use is on line 4; ask for its declaration. + let location = checkResults.GetDeclarationLocation(4, 16, "let r = MyRecord(1, 2)", [ "MyRecord" ]) + match location with + | FindDeclResult.DeclFound r -> Assert.Equal(3, r.StartLine) // 'type MyRecord = ...' + | other -> failwith $"Expected the record type declaration, got {other}" + +[] +let ``Tooltip on a record constructor call mentions the record type`` () = + let tooltip = + Checker.getTooltipWithOptions [| "--langversion:preview" |] """ +module M +type MyRecord = { A: int; B: int } +let r = MyReco{caret}rd(1, 2) +""" + Assert.Contains("MyRecord", tooltipToString tooltip) + +[] +let ``A record constructor call is linked to the record type declaration`` () = + let _, checkResults = parseAndCheckScriptPreview("Test.fsx", source) + // The constructor use is on line 4 starting at column 8 ('let r = '). + let ctorUse = + checkResults.GetAllUsesOfAllSymbolsInFile() + |> Seq.find (fun u -> u.Range.StartLine = 4 && u.Range.StartColumn = 8) + // The symbol resolves back to the record type declaration on line 3. + match ctorUse.Symbol.DeclarationLocation with + | Some loc -> Assert.Equal(3, loc.StartLine) + | None -> failwith "Expected a declaration location for the record constructor symbol" + Assert.True(checkResults.GetUsesOfSymbolInFile(ctorUse.Symbol).Length >= 1) From 249c623abfb28269410eca6573d7987c65e8e654 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Mon, 22 Jun 2026 12:51:04 +0100 Subject: [PATCH 07/10] Test record constructor on a RequireQualifiedAccess record Regression guard: the positional constructor has no field labels, so a [] record must construct without a spurious diagnostic. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Conformance/Types/RecordTypes/RecordTypes.fs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs index bba318ac0bc..f0d0fde32b7 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs @@ -756,3 +756,17 @@ let r = R() |> typecheck |> shouldFail |> withSingleDiagnostic (Error 501, Line 4, Col 9, Line 4, Col 12, "The object constructor 'R' takes 2 argument(s) but is here given 0. The required signature is 'R(A: int, B: int) : R'.") + + // A [] record forces field *labels* to be qualified in { } construction; + // the positional constructor has no labels, so it must work without any spurious RQA diagnostic. + [] + let ``Record constructor works on a RequireQualifiedAccess record`` () = + Fsx """ +[] +type R = { A : int; B : int } +let r = R(1, 2) +if r.A <> 1 || r.B <> 2 then failwith "wrong field values" + """ + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed From 06a1bf66c0c4b2cbd058fab4db7bb4583b6ac07f Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Mon, 22 Jun 2026 15:53:04 +0100 Subject: [PATCH 08/10] Add CompilerCompat cross-compiler tests for record constructors (FS-1073) Library exposes a record and an inline constructor function; the app constructs records positionally and via the inline function. The new syntax is gated behind RECORD_CTOR_FEATURE / --langversion:preview for local builds, with a classic { } fallback so SDK-compiler scenarios still build. Both branches elaborate to the same record-allocation node, so the pickled representation is unchanged and the matrix exercises both directions (feature-enabled consumer, older consumer). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../CompilerCompatApp.fsproj | 6 +++++ .../CompilerCompatApp/Program.fs | 24 +++++++++++++++++-- .../CompilerCompatLib.fsproj | 6 +++++ .../CompilerCompatLib/Library.fs | 15 ++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj b/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj index 311c09b259b..48e47fe5e17 100644 --- a/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj +++ b/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj @@ -19,6 +19,12 @@ + + + preview + $(DefineConstants);RECORD_CTOR_FEATURE + + diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs index e7bd46a8ed8..1eb2f2c2c54 100644 --- a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs +++ b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs @@ -68,8 +68,28 @@ let main _argv = printfn "ERROR: Processed result doesn't match expected" 1 else - printfn "SUCCESS: All compiler compatibility tests passed" - 0 + // FS-1073 record-constructor cross-compiler checks. + // 'makeRecordCtorPoint' is an inline function in the library; if the library was built with + // the local compiler it was authored with the new positional-ctor syntax, and this app + // (possibly an older compiler) must be able to inline its pickled body correctly. + let viaInline = Library.makeRecordCtorPoint 7 9 + // Direct construction of a packaged record. When this app is built with the local compiler + // it uses the new positional constructor on a record defined in another assembly. +#if RECORD_CTOR_FEATURE + let viaCtor = Library.RecordCtorPoint(3, 4) +#else + let viaCtor = { Library.RecordCtorPoint.A = 3; Library.RecordCtorPoint.B = 4 } +#endif + if viaInline.A <> 7 || viaInline.B <> 9 then + printfn "ERROR: inline record constructor result mismatch" + 1 + elif viaCtor.A <> 3 || viaCtor.B <> 4 then + printfn "ERROR: record constructor result mismatch" + 1 + else + printfn "RecordCtor: inline=(%d,%d) direct=(%d,%d)" viaInline.A viaInline.B viaCtor.A viaCtor.B + printfn "SUCCESS: All compiler compatibility tests passed" + 0 with ex -> printfn "ERROR: Exception occurred: %s" ex.Message diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj b/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj index a9442854e07..364eca5b04c 100644 --- a/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj +++ b/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj @@ -28,6 +28,12 @@ + + + preview + $(DefineConstants);RECORD_CTOR_FEATURE + + diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs index 625cb787b58..3683777db98 100644 --- a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs +++ b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs @@ -55,3 +55,18 @@ module Library = [] type TypeWithLiteralAttrArg() = member _.GetValue() = LiteralAttrArg + + /// Plain record used to test the FS-1073 positional record constructor across compiler versions. + /// The record itself is ordinary and compiles on any compiler; only the *construction* syntax is new. + type RecordCtorPoint = { A: int; B: int } + + /// inline function that constructs the record. When this library is built with the local compiler + /// (RECORD_CTOR_FEATURE defined), it uses the new positional constructor; otherwise classic record + /// syntax. Both elaborate to the same record-allocation node, so the pickled inline body must be + /// consumable by an app built with an older compiler (the FS-1073 cross-compiler concern). + let inline makeRecordCtorPoint a b = +#if RECORD_CTOR_FEATURE + RecordCtorPoint(a, b) +#else + { RecordCtorPoint.A = a; RecordCtorPoint.B = b } +#endif From 0797866c511228021f1ef73918fe09a1a5b2bc59 Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Mon, 22 Jun 2026 16:13:18 +0100 Subject: [PATCH 09/10] Test record constructor signature-file interplay The constructor is not a declared member, so it rides on the record's representation visibility through a signature: available when the .fsi exposes the representation, rejected (FS1133, like { }) when the .fsi hides it. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Types/RecordTypes/RecordTypes.fs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs index f0d0fde32b7..be1d260f776 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs @@ -770,3 +770,47 @@ if r.A <> 1 || r.B <> 2 then failwith "wrong field values" |> withLangVersionPreview |> compileExeAndRun |> shouldSucceed + + // Signature-file interplay: the constructor is not a declared member, so it is never written in a + // .fsi - it rides on the visibility of the record's representation, exactly like { } construction. + [] + let ``Record constructor is available when the signature exposes the record representation`` () = + Fsi """ +module Lib +type R = { A: int; B: int } +""" + |> withAdditionalSourceFiles [ + FsSource """ +module Lib +type R = { A: int; B: int } +""" + FsSourceWithFileName "Consumer.fs" """ +module Consumer +let r = Lib.R(1, 2) +if r.A <> 1 || r.B <> 2 then failwith "wrong field values" +""" + ] + |> withLangVersionPreview + |> compile + |> shouldSucceed + + [] + let ``Record constructor is unavailable when the signature hides the record representation`` () = + Fsi """ +module Lib +type R +""" + |> withAdditionalSourceFiles [ + FsSource """ +module Lib +type R = { A: int; B: int } +""" + FsSourceWithFileName "Consumer.fs" """ +module Consumer +let _ = Lib.R(1, 2) +""" + ] + |> withLangVersionPreview + |> compile + |> shouldFail + |> withErrorCode 1133 From 809375774b0f122e5a90c075c90b706b81983afe Mon Sep 17 00:00:00 2001 From: Charles Roddie Date: Thu, 25 Jun 2026 19:51:56 +0100 Subject: [PATCH 10/10] CompilerCompat: generalize define to USES_PREVIEW_COMPILER, drop comments Per review: rename the per-feature RECORD_CTOR_FEATURE define to the reusable USES_PREVIEW_COMPILER, and remove the explanatory comments so the addition is compact. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../CompilerCompatApp/CompilerCompatApp.fsproj | 4 ++-- .../CompilerCompat/CompilerCompatApp/Program.fs | 8 +------- .../CompilerCompatLib/CompilerCompatLib.fsproj | 4 ++-- .../CompilerCompat/CompilerCompatLib/Library.fs | 10 +++------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj b/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj index 48e47fe5e17..babd856f3e6 100644 --- a/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj +++ b/tests/projects/CompilerCompat/CompilerCompatApp/CompilerCompatApp.fsproj @@ -19,10 +19,10 @@ - + preview - $(DefineConstants);RECORD_CTOR_FEATURE + $(DefineConstants);USES_PREVIEW_COMPILER diff --git a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs index 1eb2f2c2c54..7248a62a30c 100644 --- a/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs +++ b/tests/projects/CompilerCompat/CompilerCompatApp/Program.fs @@ -68,14 +68,8 @@ let main _argv = printfn "ERROR: Processed result doesn't match expected" 1 else - // FS-1073 record-constructor cross-compiler checks. - // 'makeRecordCtorPoint' is an inline function in the library; if the library was built with - // the local compiler it was authored with the new positional-ctor syntax, and this app - // (possibly an older compiler) must be able to inline its pickled body correctly. let viaInline = Library.makeRecordCtorPoint 7 9 - // Direct construction of a packaged record. When this app is built with the local compiler - // it uses the new positional constructor on a record defined in another assembly. -#if RECORD_CTOR_FEATURE +#if USES_PREVIEW_COMPILER let viaCtor = Library.RecordCtorPoint(3, 4) #else let viaCtor = { Library.RecordCtorPoint.A = 3; Library.RecordCtorPoint.B = 4 } diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj b/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj index 364eca5b04c..f5c46447238 100644 --- a/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj +++ b/tests/projects/CompilerCompat/CompilerCompatLib/CompilerCompatLib.fsproj @@ -28,10 +28,10 @@ - + preview - $(DefineConstants);RECORD_CTOR_FEATURE + $(DefineConstants);USES_PREVIEW_COMPILER diff --git a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs index 3683777db98..48f4236beba 100644 --- a/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs +++ b/tests/projects/CompilerCompat/CompilerCompatLib/Library.fs @@ -56,16 +56,12 @@ module Library = type TypeWithLiteralAttrArg() = member _.GetValue() = LiteralAttrArg - /// Plain record used to test the FS-1073 positional record constructor across compiler versions. - /// The record itself is ordinary and compiles on any compiler; only the *construction* syntax is new. + /// Record + inline constructor for the FS-1073 cross-compiler test. The new positional syntax is used + /// when built with a preview compiler, classic syntax otherwise; both pickle identically. type RecordCtorPoint = { A: int; B: int } - /// inline function that constructs the record. When this library is built with the local compiler - /// (RECORD_CTOR_FEATURE defined), it uses the new positional constructor; otherwise classic record - /// syntax. Both elaborate to the same record-allocation node, so the pickled inline body must be - /// consumable by an app built with an older compiler (the FS-1073 cross-compiler concern). let inline makeRecordCtorPoint a b = -#if RECORD_CTOR_FEATURE +#if USES_PREVIEW_COMPILER RecordCtorPoint(a, b) #else { RecordCtorPoint.A = a; RecordCtorPoint.B = b }