From 38f8254d4b0a130331e02982b3635455262aeecb Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Mon, 26 Oct 2026 11:06:32 +0100 Subject: [PATCH 1/3] Checker: check record spreads once --- .../.FSharp.Compiler.Service/11.0.200.md | 1 + src/Compiler/Checking/CheckDeclarations.fs | 241 ++++++++---------- .../Checking/Expressions/CheckExpressions.fs | 11 +- src/Compiler/Checking/Spreads.fs | 155 +++++------ .../FSharpChecker/CommonWorkflows.fs | 2 +- .../Language/RecordSpreadsTests.fs | 6 +- .../ProjectAnalysisTests.fs | 12 +- .../FSharp.Compiler.Service.Tests/Symbols.fs | 77 ++++++ 8 files changed, 255 insertions(+), 250 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md index a0d99dfebfa..405f8ee25c1 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md @@ -31,6 +31,7 @@ * Preserve SRTP extension scopes across source files and competing witnesses through imported inline calls. Compile generic extension forwarding, including quotations, in unoptimized builds. Preserve applicable built-in operator priority in imported inline quotations. Do not reuse SRTP specializations across different selected witnesses. ([Issue #20683](https://github.com/dotnet/fsharp/issues/20683), [Issue #20684](https://github.com/dotnet/fsharp/issues/20684), [Issue #20685](https://github.com/dotnet/fsharp/issues/20685), [PR #20717](https://github.com/dotnet/fsharp/pull/20717)) * Transparent compiler: with `enablePartialTypeChecking`, a project that is only referenced by another project is type checked from its signatures. Implementation files that have a signature file are no longer parsed or type checked to produce the referenced assembly. `ParseAndCheckProject` still checks all files. ([PR #20703](https://github.com/dotnet/fsharp/pull/20703)) * With `enablePartialTypeChecking`, a project that is only referenced by another project no longer type checks its implementation files that have a signature file. Producing the referenced assembly only needs the signatures, but finalizing the project also collected the diagnostics of the skipped files, which type checked all of them and kept their results in memory. `ParseAndCheckProject` still reports the diagnostics of these files. ([PR #20699](https://github.com/dotnet/fsharp/pull/20699)) +* Checker: check record spreads once ### Changed diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index df9037a8d47..624953794cb 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -2641,6 +2641,7 @@ module EstablishTypeDefinitionCores = /// /// Used when determining if a structural type supports structural comparison. let private GetStructuralElementsOfTyconDefn (cenv: cenv) env tpenv (MutRecDefnsPhase1DataForTycon(_, synTyconRepr, _, _, _, _)) tycon = + let cenv = { cenv with tcSink = TcResultsSink.NoSink } let thisTyconRef = mkLocalTyconRef tycon let g = cenv.g let env = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) env @@ -2691,27 +2692,35 @@ module EstablishTypeDefinitionCores = yield (ty, m) | SynTypeDefnSimpleRepr.Record (_, fieldsAndSpreads, _) -> - let tcField (SynField (fieldType = ty; range = m)) = - let tyR, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty - (tyR, m), ignore, ignore + let fieldsAndSpreads = + fieldsAndSpreads + |> List.choose (fun fieldOrSpread -> + match fieldOrSpread with + | SynFieldOrSpread.Field (SynField (idOpt = Some fieldId; fieldType = ty; range = m)) -> + let tyR, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty + Some (ExplicitOrSpread.Explicit (fieldId.idText, (tyR, m))) - let tcSpread (SynTypeSpread (ty = ty; range = m)) = - let spreadSrcTy, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty + | SynFieldOrSpread.Field (SynField (idOpt = None)) -> None + | SynFieldOrSpread.Spread (SynTypeSpread (ty = ty; range = m)) -> + let spreadSrcTy, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty + Some (ExplicitOrSpread.Spread (spreadSrcTy, m))) + + let fieldOfExplicit (fieldName, fieldTyAndRange) = fieldName, fieldTyAndRange, ignore, ignore + + let fieldsOfSpread (spreadSrcTy, m) = if isRecdTy g spreadSrcTy then spreadSrcTys.Add spreadSrcTy - ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad spreadSrcTy false - |> List.choose (function - | Item.RecdField field -> Some (field.RecdField.Id.idText, (field.FieldType, m), ignore, ignore) - | _ -> None) - else - match tryDestAnonRecdTy g spreadSrcTy with - | ValueSome (anonInfo, tys) -> tys |> List.mapi (fun i ty -> (anonInfo.SortedNames[i], (ty, m), ignore, ignore)) - | ValueNone -> [] + + Spreads.fieldItemsOfSpreadSrcTy cenv.nameResolver m ad spreadSrcTy + |> List.choose (function + | Item.RecdField field -> Some (field.RecdField.Id.idText, (field.FieldType, m), ignore, ignore) + | Item.AnonRecdField (anonInfo, tys, i, _) -> Some (anonInfo.SortedNames[i], (tys[i], m), ignore, ignore) + | _ -> None) // We must apply the spread shadowing logic here // to get the correct set of field types. - yield! fieldsAndSpreads |> Spreads.Types.Records.check ignore tcField tcSpread + yield! fieldsAndSpreads |> Spreads.Types.Records.check fieldOfExplicit fieldsOfSpread | _ -> () ], spreadSrcTys @@ -3740,34 +3749,34 @@ module EstablishTypeDefinitionCores = structLayoutAttributeCheck true // these are allowed for records recordExtendedLayoutAttributeCheck() - let check pass = - let firstPass = pass = FirstPass - let recdFields = - let tcField synField = - let field = TcRecdUnionAndEnumDeclarations.TcNamedFieldDecl cenv envinner innerParent false tpenv addFixup synField |> Option.get - let errorAmbiguousShadowing () = if firstPass then errorR (Duplicate ("field", field.Id.idText, field.Id.idRange)) - let infoExplicitShadowing () = - if firstPass then - let fmtedSpreadField = NicePrint.stringOfRecdField envinner.DisplayEnv cenv.infoReader thisTyconRef field - informationalWarning (Error (FSComp.SR.tcRecordExplicitFieldShadowsSpreadField fmtedSpreadField, field.Id.idRange)) - - field, errorAmbiguousShadowing, infoExplicitShadowing - - let tcSpread (SynTypeSpread (ty = ty; range = m)) = - let mTy = ty.Range - let (spreadSrcTy, _tpenv), error = - try TcType cenv NoNewTypars CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes envinner tpenv ty, false with + let checkedFieldsAndSpreads = + fieldsAndSpreads + |> List.choose (fun fieldOrSpread -> + match fieldOrSpread with + | SynFieldOrSpread.Field synField -> + TcRecdUnionAndEnumDeclarations.TcNamedFieldDecl cenv envinner innerParent false tpenv addFixup synField + |> Option.map ExplicitOrSpread.Explicit + + | SynFieldOrSpread.Spread (SynTypeSpread (ty = ty; range = m)) -> + checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RecordSpreads m + + let spreadSrcTy = + try + let spreadSrcTy, _ = TcType cenv NoNewTypars CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes envinner tpenv ty + ValueSome spreadSrcTy + with | RecoverableException e -> - if firstPass then - errorRecovery e ty.Range - (g.obj_ty_ambivalent, tpenv), true + errorRecovery e ty.Range + ValueNone - let spreadSrcTyIsNullable = g.checkNullness && (nullnessOfTy g spreadSrcTy).Evaluate() = NullnessInfo.WithNull - let spreadSrcTyIsRecd = error || isRecdTy g spreadSrcTy || isAnonRecdTy g spreadSrcTy + match spreadSrcTy with + | ValueNone -> None + | ValueSome spreadSrcTy -> - let isValidSpreadSrcTy = not spreadSrcTyIsNullable && spreadSrcTyIsRecd + let spreadSrcTyIsNullable = g.checkNullness && (nullnessOfTy g spreadSrcTy).Evaluate() = NullnessInfo.WithNull + let spreadSrcTyIsRecd = isRecdTy g spreadSrcTy || isAnonRecdTy g spreadSrcTy - if isValidSpreadSrcTy then + if spreadSrcTyIsRecd && not spreadSrcTyIsNullable then let spreadSrcTy = tryAppTy g spreadSrcTy |> ValueOption.map (fun (tcref, tinst) -> @@ -3776,99 +3785,63 @@ module EstablishTypeDefinitionCores = TType_app (tcref, newTinst, g.knownWithoutNull)) |> ValueOption.defaultValue spreadSrcTy - let recordFieldsFromSpread = - if isRecdTy g spreadSrcTy then - ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad spreadSrcTy false - else - tryDestAnonRecdTy g spreadSrcTy - |> ValueOption.map (fun (anonInfo, tys) -> - anonInfo.SortedIds - |> List.ofArray - |> List.mapi (fun i id -> Item.AnonRecdField (anonInfo, tys, i, id.idRange))) - |> ValueOption.defaultValue [] - - recordFieldsFromSpread - |> List.choose (fun field -> - match field with - | Item.RecdField fieldInfo -> - // Update the field ID's range to be that of the spread. - let syntheticId = ident (fieldInfo.RecdField.Id.idText, mTy) - let fieldTy = fieldInfo.FieldType - let vis = - let vis, _ = ComputeAccessAndCompPath g envinner None mTy None None innerParent - combineAccess vis thisTyconRef.TypeReprAccessibility - - let recdField = - { fieldInfo.RecdField with - rfield_id = syntheticId - rfield_type = fieldTy - rfield_access = vis } - - let warnAmbiguousShadowing () = - let fmtedSpreadField = NicePrint.stringOfRecdField envinner.DisplayEnv cenv.infoReader fieldInfo.TyconRef recdField - let fmtedSpreadSrcTy = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy - warning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsExplicitField (fmtedSpreadField, fmtedSpreadSrcTy), m)) - - let infoSpreadShadowing () = - let fmtedSpreadField = NicePrint.stringOfRecdField envinner.DisplayEnv cenv.infoReader fieldInfo.TyconRef recdField - let fmtedSpreadSrcTy = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy - informationalWarning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsSpreadField (fmtedSpreadField, fmtedSpreadSrcTy), m)) - - Some (fieldInfo.RecdField.Id.idText, recdField, warnAmbiguousShadowing, infoSpreadShadowing) - - | Item.AnonRecdField (anonInfo, tys, fieldIndex, _) -> - let fieldId = - let orig = anonInfo.SortedIds[fieldIndex] - ident (orig.idText, m) - - let ty = tys[fieldIndex] - - let field = - let stat = false - let konst = None - let generated = false - let mut = false - let volatile = false - let pattribs = [] - let fattribs = [] - let vis = None - TcRecdUnionAndEnumDeclarations.MakeRecdFieldSpec g envinner innerParent (stat, konst, ty, pattribs, fattribs, fieldId, generated, mut, volatile, XmlDoc.Empty, vis, mTy) - - let warnAmbiguousShadowing () = - let typars = tryAppTy g ty |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption)) |> ValueOption.defaultValue [] - let fmtedSpreadField = LayoutRender.showL (NicePrint.prettyLayoutOfMemberSig envinner.DisplayEnv ([], fieldId.idText, typars, [], ty)) - let fmtedSpreadSrcTy = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy - warning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsExplicitField (fmtedSpreadField, fmtedSpreadSrcTy), m)) - - let infoSpreadShadowing () = - let typars = tryAppTy g ty |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption)) |> ValueOption.defaultValue [] - let fmtedSpreadField = LayoutRender.showL (NicePrint.prettyLayoutOfMemberSig envinner.DisplayEnv ([], fieldId.idText, typars, [], ty)) - let fmtedSpreadSrcTy = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy - informationalWarning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsSpreadField (fmtedSpreadField, fmtedSpreadSrcTy), m)) - - Some (fieldId.idText, field, warnAmbiguousShadowing, infoSpreadShadowing) - - | _ -> None) - elif not firstPass then - [] + Some (ExplicitOrSpread.Spread (spreadSrcTy, m)) else if not ty.IsFromParseError then if not spreadSrcTyIsRecd then errorR (Error (FSComp.SR.tcRecordTypeDefinitionSpreadSourceMustBeRecord (), m)) elif spreadSrcTyIsNullable then errorR (Error (FSComp.SR.tcRecordTypeDefinitionSpreadSourceCannotBeNullable (), m)) - [] - let checkSpreadsLanguageFeature m = - if firstPass then - checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RecordSpreads m + None) - fieldsAndSpreads |> Spreads.Types.Records.check checkSpreadsLanguageFeature tcField tcSpread + let hasSpreads = checkedFieldsAndSpreads |> List.exists _.IsSpread - writeFakeRecordFieldsToSink recdFields - CallEnvSink cenv.tcSink (mRepr, envinner.NameEnv, ad) + let expandFields pass = + let isFinalPass = pass = SecondPass || not hasSpreads + let stringOfField = NicePrint.stringOfRecdField envinner.DisplayEnv cenv.infoReader thisTyconRef - let data = + let fieldOfExplicit (field: RecdField) = + let errorAmbiguousShadowing () = + if isFinalPass then + errorR (Duplicate ("field", field.Id.idText, field.Id.idRange)) + + let infoExplicitShadowing () = + if isFinalPass then + informationalWarning (Error (FSComp.SR.tcRecordExplicitFieldShadowsSpreadField (stringOfField field), field.Id.idRange)) + + field.Id.idText, field, errorAmbiguousShadowing, infoExplicitShadowing + + let fieldsOfSpread (spreadSrcTy, m) = + let fmtedSpreadSrcTy () = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy + + let mkSpreadField (fieldId: Ident) ty isMutable pattribs fattribs xmldoc = + TcRecdUnionAndEnumDeclarations.MakeRecdFieldSpec g envinner innerParent (false, None, ty, pattribs, fattribs, fieldId.MakeSynthetic(), false, isMutable, false, xmldoc, None, m) + + Spreads.fieldItemsOfSpreadSrcTy cenv.nameResolver m ad spreadSrcTy + |> List.choose (fun fieldItem -> + match fieldItem with + | Item.RecdField fieldInfo -> + let field = fieldInfo.RecdField + Some (mkSpreadField field.Id fieldInfo.FieldType field.IsMutable field.PropertyAttribs field.FieldAttribs field.XmlDoc) + | Item.AnonRecdField (anonInfo, tys, fieldIndex, _) -> + Some (mkSpreadField anonInfo.SortedIds[fieldIndex] tys[fieldIndex] false [] [] XmlDoc.Empty) + | _ -> None) + |> List.map (fun field -> + let warnAmbiguousShadowing () = + if isFinalPass then + warning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsExplicitField (stringOfField field, fmtedSpreadSrcTy ()), m)) + + let infoSpreadShadowing () = + if isFinalPass then + informationalWarning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsSpreadField (stringOfField field, fmtedSpreadSrcTy ()), m)) + + field.Id.idText, field, warnAmbiguousShadowing, infoSpreadShadowing) + + checkedFieldsAndSpreads |> Spreads.Types.Records.check fieldOfExplicit fieldsOfSpread + + let mkRepr recdFields = + TFSharpTyconRepr { fsobjmodel_cases = Construct.MakeUnionCases [] fsobjmodel_kind = TFSharpRecord @@ -3876,19 +3849,17 @@ module EstablishTypeDefinitionCores = fsobjmodel_rfields = Construct.MakeRecdFieldsTable recdFields } - let repr = TFSharpTyconRepr data - repr, None, NoSafeInitInfo + let recdFields = expandFields FirstPass + writeFakeRecordFieldsToSink recdFields + CallEnvSink cenv.tcSink (mRepr, envinner.NameEnv, ad) let recheck = - if fieldsAndSpreads |> List.exists (function SynFieldOrSpread.Spread _ -> true | SynFieldOrSpread.Field _ -> false) then - fun () -> - let repr, _, _ = check SecondPass - tycon.entity_tycon_repr <- repr + if hasSpreads then + fun () -> tycon.entity_tycon_repr <- mkRepr (expandFields SecondPass) else ignore - - check FirstPass, recheck + (mkRepr recdFields, None, NoSafeInitInfo), recheck | SynTypeDefnSimpleRepr.LibraryOnlyILAssembly (s, _) -> let s = (s :?> ILType) @@ -4443,6 +4414,8 @@ module EstablishTypeDefinitionCores = ] // Check for cyclic spreads. + let tyconsInSpreadCycles = HashSet () + do if cenv.g.langVersion.SupportsFeature LanguageFeature.RecordSpreads then let (|PotentiallyRecursiveTycon|_|) ty = @@ -4469,7 +4442,11 @@ module EstablishTypeDefinitionCores = ] let graph = Graph (_.Stamp, tycons, edges) - graph.IterateCycles (fun path -> errorR (Error (FSComp.SR.tcTypeDefinitionIsCyclicThroughSpreads (), (List.head path).Range))) + graph.IterateCycles (fun path -> + errorR (Error (FSComp.SR.tcTypeDefinitionIsCyclicThroughSpreads (), (List.head path).Range)) + + for tycon in path do + tyconsInSpreadCycles.Add tycon.Stamp |> ignore) let scSet = TyconConstraintInference.InferSetOfTyconsSupportingComparable cenv envMutRecPrelim.DisplayEnv tyconsWithStructuralTypes let seSet = TyconConstraintInference.InferSetOfTyconsSupportingEquatable cenv envMutRecPrelim.DisplayEnv tyconsWithStructuralTypes @@ -4532,8 +4509,8 @@ module EstablishTypeDefinitionCores = [ for _, tyconOpt, _, (_, _, recheck) in passOne |> MutRecShapes.collectTycons do match tyconOpt with - | Some tycon -> tycon.Stamp, recheck - | None -> () + | Some tycon when not (tyconsInSpreadCycles.Contains tycon.Stamp) -> tycon.Stamp, recheck + | _ -> () ] let spreadDependencies = diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index d26dc8d0f63..3e07888115d 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -1999,8 +1999,9 @@ let BuildFieldMap (cenv: cenv) env isPartial ty (flds: (Ident * ExplicitOrSpread | [FieldResolution(rfinfo2, showDeprecated)] -> // Record the precise resolution of the field for intellisense - let item = Item.RecdField(rfinfo2) - CallNameResolutionSink cenv.tcSink (ident.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, ad) + if fldExpr.IsExplicit then + let item = Item.RecdField(rfinfo2) + CallNameResolutionSink cenv.tcSink (ident.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, ad) let fref2 = rfinfo2.RecdFieldRef @@ -8163,11 +8164,11 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m // Check if the overall type is an anon record type and if so raise an copy-update syntax error // let f (r: {| A: int; C: int |}) = { r with A = 1; B = 2; C = 3 } if isAnonRecdTy cenv.g overallTy || isStructAnonRecdTy cenv.g overallTy then - for fldId, _ in flds do + for fldId, fld in flds do match TryFindAnonRecdFieldOfType g overallTy fldId.idText with - | Some item -> + | Some item when fld.IsExplicit -> CallNameResolutionSink cenv.tcSink (fldId.idRange, env.eNameResEnv, item, emptyTyparInst, ItemOccurrence.UseInType, env.eAccessRights) - | None -> () + | _ -> () let firstPartRange = withStartEnd mWholeExpr.Start (mkPos mWholeExpr.StartLine (mWholeExpr.StartColumn + 1)) mWholeExpr // Use the left { in the expression errorR(Error(FSComp.SR.chkCopyUpdateSyntaxInAnonRecords(), firstPartRange)) diff --git a/src/Compiler/Checking/Spreads.fs b/src/Compiler/Checking/Spreads.fs index 4c40f7875ed..4b42f20964d 100644 --- a/src/Compiler/Checking/Spreads.fs +++ b/src/Compiler/Checking/Spreads.fs @@ -47,14 +47,39 @@ let private (|NestedUpdate|_|) expr2 expr1 = Some(SynExpr.AnonRecd(isStruct, copyInfo, fields1 @ fields2, m, trivia)) | _ -> None +/// The fields of a nominal or anonymous record type, as name resolution items. +let fieldItemsOfSpreadSrcTy (ncenv: NameResolver) m ad spreadSrcTy = + let g = ncenv.g + + if isRecdTy g spreadSrcTy then + ResolveRecordOrClassFieldsOfType ncenv m ad spreadSrcTy false + else + tryDestAnonRecdTy g spreadSrcTy + |> ValueOption.map (fun (anonInfo, tys) -> + anonInfo.SortedIds + |> List.ofArray + |> List.mapi (fun i id -> Item.AnonRecdField(anonInfo, tys, i, id.idRange))) + |> ValueOption.defaultValue [] + +/// Formats a field of an anonymous record type for a diagnostic. +let private stringOfAnonRecdField (denv: DisplayEnv) fieldName ty = + let g = denv.g + + let typars = + tryAppTy g ty + |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption)) + |> ValueOption.defaultValue [] + + LayoutRender.showL (NicePrint.prettyLayoutOfMemberSig denv ([], fieldName, typars, [], ty)) + /// Functions for checking type spreads. [] module Types = /// Functions for checking record type spreads. [] module Records = - /// Typechecks the given list of record fields or spreads. - let check checkSpreadsLanguageFeature tcField tcSpread (fieldsAndSpreads: SynFieldOrSpread list) : _ list = + /// Collects the fields of a record type definition from its explicit fields and spreads, applying the shadowing rules. + let check fieldOfExplicit fieldsOfSpread (fieldsAndSpreads: ExplicitOrSpread<'Explicit, 'Spread> list) : _ list = let rec loop fields i fieldsAndSpreads = match fieldsAndSpreads with | [] -> @@ -64,14 +89,13 @@ module Types = |> List.sortBy (fun (i, _) -> i) |> List.map (fun (_, r) -> r) - | SynFieldOrSpread.Field(SynField(idOpt = None)) :: fieldsAndSpreads -> loop fields i fieldsAndSpreads - - | SynFieldOrSpread.Field(SynField(idOpt = Some fieldId) as synField) :: fieldsAndSpreads -> - let field, errorAmbiguousShadowing, infoExplicitShadowing = tcField synField + | ExplicitOrSpread.Explicit explicitField :: fieldsAndSpreads -> + let fieldName, field, errorAmbiguousShadowing, infoExplicitShadowing = + fieldOfExplicit explicitField let fields = fields - |> Map.change fieldId.idText (function + |> Map.change fieldName (function | None -> Some(LeftwardExplicit, [ i, field ]) | Some(LeftwardExplicit, dupes) -> errorAmbiguousShadowing () @@ -82,16 +106,14 @@ module Types = loop fields (i + 1) fieldsAndSpreads - | SynFieldOrSpread.Spread(SynTypeSpread(range = m) as synSpread) :: fieldsAndSpreads -> - checkSpreadsLanguageFeature m - + | ExplicitOrSpread.Spread spread :: fieldsAndSpreads -> let rec collectFieldsFromSpread fields i fieldsFromSpread = match fieldsFromSpread with | [] -> fields, i - | (fieldId, field, warnAmbiguousShadowing, infoSpreadShadowing) :: fieldsFromSpread -> + | (fieldName, field, warnAmbiguousShadowing, infoSpreadShadowing) :: fieldsFromSpread -> let fields = fields - |> Map.change fieldId (function + |> Map.change fieldName (function | None -> Some(NoLeftwardExplicit, [ i, field ]) | Some(LeftwardExplicit, _dupes) -> warnAmbiguousShadowing () @@ -102,7 +124,7 @@ module Types = collectFieldsFromSpread fields (i + 1) fieldsFromSpread - let fields, i = collectFieldsFromSpread fields i (tcSpread synSpread) + let fields, i = collectFieldsFromSpread fields i (fieldsOfSpread spread) loop fields i fieldsAndSpreads loop Map.empty 0 fieldsAndSpreads @@ -110,6 +132,15 @@ module Types = /// Functions for checking value spreads. [] module Values = + let private shadowingDiagnostics m (fmtedSpreadField: unit -> string) = + let warnAmbiguousShadowing () = + warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField (fmtedSpreadField ()), m)) + + let infoSpreadShadowing () = + informationalWarning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsSpreadField (fmtedSpreadField ()), m)) + + warnAmbiguousShadowing, infoSpreadShadowing + /// Functions for checking record spreads. [] module Records = @@ -288,19 +319,8 @@ module Values = spreadSrcAddrExpr, (fun expr -> wrap (mkCompGenLet m spreadSrcAddrVal oldAddr expr)) - let recordFieldsFromSpread = - if isRecdTy g tyOfSpreadSrcExpr then - ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad tyOfSpreadSrcExpr false - else - tryDestAnonRecdTy g tyOfSpreadSrcExpr - |> ValueOption.map (fun (anonInfo, tys) -> - anonInfo.SortedIds - |> List.ofArray - |> List.mapi (fun i id -> Item.AnonRecdField(anonInfo, tys, i, id.idRange))) - |> ValueOption.defaultValue [] - let fields = - recordFieldsFromSpread + fieldItemsOfSpreadSrcTy cenv.nameResolver m ad tyOfSpreadSrcExpr |> List.choose (fun field -> match field with | Item.RecdField fieldInfo -> @@ -310,17 +330,9 @@ module Values = let fieldId = ident (fieldInfo.RecdField.Id.idText, mExpr) let ty = fieldInfo.FieldType - let warnAmbiguousShadowing () = - let fmtedSpreadField = - NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField - - warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField fmtedSpreadField, m)) - - let infoSpreadShadowing () = - let fmtedSpreadField = - NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField - - informationalWarning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsSpreadField fmtedSpreadField, m)) + let warnAmbiguousShadowing, infoSpreadShadowing = + shadowingDiagnostics m (fun () -> + NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField) Some(fieldId, ExplicitOrSpread.Spread(ty, fieldExpr), warnAmbiguousShadowing, infoSpreadShadowing) @@ -328,26 +340,11 @@ module Values = let fieldExpr = mkAnonRecdFieldGet g (anonInfo, spreadSrcAddrExpr, tys, fieldIndex, mExpr) - let fieldId = anonInfo.SortedIds[fieldIndex] + let fieldId = ident (anonInfo.SortedNames[fieldIndex], mExpr) let ty = tys[fieldIndex] - let getFmtedSpreadField () = - let typars = - tryAppTy g ty - |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption)) - |> ValueOption.defaultValue [] - - LayoutRender.showL ( - NicePrint.prettyLayoutOfMemberSig env.DisplayEnv ([], fieldId.idText, typars, [], ty) - ) - - let warnAmbiguousShadowing () = - warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField (getFmtedSpreadField ()), m)) - - let infoSpreadShadowing () = - informationalWarning ( - Error(FSComp.SR.tcRecordExprSpreadFieldShadowsSpreadField (getFmtedSpreadField ()), m) - ) + let warnAmbiguousShadowing, infoSpreadShadowing = + shadowingDiagnostics m (fun () -> stringOfAnonRecdField env.DisplayEnv fieldId.idText ty) Some(fieldId, ExplicitOrSpread.Spread(ty, fieldExpr), warnAmbiguousShadowing, infoSpreadShadowing) @@ -585,23 +582,12 @@ module Values = spreadSrcAddrExpr, (fun expr -> wrap (mkCompGenLet m spreadSrcAddrVal oldAddr expr)) - let recordFieldsFromSpread = - if isRecdTy g tyOfSpreadSrcExpr then - ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad tyOfSpreadSrcExpr false - else - tryDestAnonRecdTy g tyOfSpreadSrcExpr - |> ValueOption.map (fun (anonInfo, tys) -> - anonInfo.SortedIds - |> List.ofArray - |> List.mapi (fun i id -> Item.AnonRecdField(anonInfo, tys, i, id.idRange))) - |> ValueOption.defaultValue [] - let fields = - recordFieldsFromSpread + fieldItemsOfSpreadSrcTy cenv.nameResolver m ad tyOfSpreadSrcExpr |> List.choose (fun field -> match field with | Item.RecdField fieldInfo -> - let fieldId = fieldInfo.RecdField.Id + let fieldId = fieldInfo.RecdField.Id.MakeSynthetic() let ty = possibleTargetTyAt fieldId.idText @@ -620,22 +606,14 @@ module Values = let fieldExpr = mkCoerceIfNeeded g ty (tyOfExpr g fieldExpr) fieldExpr fieldExpr - let warnAmbiguousShadowing () = - let fmtedSpreadField = - NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField - - warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField fmtedSpreadField, m)) - - let infoSpreadShadowing () = - let fmtedSpreadField = - NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField - - informationalWarning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsSpreadField fmtedSpreadField, m)) + let warnAmbiguousShadowing, infoSpreadShadowing = + shadowingDiagnostics m (fun () -> + NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField) Some(fieldId, ty, tcField, warnAmbiguousShadowing, infoSpreadShadowing) | Item.AnonRecdField(anonInfo, tys, fieldIndex, _) -> - let fieldId = anonInfo.SortedIds[fieldIndex] + let fieldId = anonInfo.SortedIds[fieldIndex].MakeSynthetic() let ty = possibleTargetTyAt fieldId.idText @@ -654,23 +632,8 @@ module Values = let fieldExpr = mkCoerceIfNeeded g ty (tyOfExpr g fieldExpr) fieldExpr fieldExpr - let getFmtedSpreadField () = - let typars = - tryAppTy g ty - |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption)) - |> ValueOption.defaultValue [] - - LayoutRender.showL ( - NicePrint.prettyLayoutOfMemberSig env.DisplayEnv ([], fieldId.idText, typars, [], ty) - ) - - let warnAmbiguousShadowing () = - warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField (getFmtedSpreadField ()), m)) - - let infoSpreadShadowing () = - informationalWarning ( - Error(FSComp.SR.tcRecordExprSpreadFieldShadowsSpreadField (getFmtedSpreadField ()), m) - ) + let warnAmbiguousShadowing, infoSpreadShadowing = + shadowingDiagnostics m (fun () -> stringOfAnonRecdField env.DisplayEnv fieldId.idText ty) Some(fieldId, ty, tcField, warnAmbiguousShadowing, infoSpreadShadowing) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs index 7de8139ac2b..f175b74c51a 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs @@ -180,7 +180,7 @@ let GetAllUsesOfAllSymbols() : Task = let! checkProjectResults = checker.ParseAndCheckProject(options) let result = checkProjectResults.GetAllUsesOfAllSymbols() - if result.Length <> 79 then failwith $"Expected 79 symbolUses, got {result.Length}:\n%A{result}" + if result.Length <> 75 then failwith $"Expected 75 symbolUses, got {result.Length}:\n%A{result}" } [] diff --git a/tests/FSharp.Compiler.ComponentTests/Language/RecordSpreadsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/RecordSpreadsTests.fs index 32bdab08ed1..396c1ec09cc 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/RecordSpreadsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/RecordSpreadsTests.fs @@ -867,7 +867,6 @@ but here has type |> withDiagnostics [ Error 3901, Line 2, Col 26, Line 2, Col 27, "This type definition involves a cyclic reference through a spread." Error 3901, Line 3, Col 26, Line 3, Col 27, "This type definition involves a cyclic reference through a spread." - Warning 3897, Line 2, Col 41, Line 2, Col 45, "Spread field 'A: int' from type 'S' shadows an explicitly declared field with the same name." ] [] @@ -890,7 +889,6 @@ but here has type Error 3901, Line 3, Col 26, Line 3, Col 27, "This type definition involves a cyclic reference through a spread." Error 3901, Line 4, Col 26, Line 4, Col 27, "This type definition involves a cyclic reference through a spread." Error 3901, Line 5, Col 26, Line 5, Col 27, "This type definition involves a cyclic reference through a spread." - Warning 3897, Line 2, Col 41, Line 2, Col 45, "Spread field 'A: int' from type 'S' shadows an explicitly declared field with the same name." ] [] @@ -911,7 +909,6 @@ but here has type |> withDiagnostics [ Error 3901, Line 4, Col 26, Line 4, Col 27, "This type definition involves a cyclic reference through a spread." Error 3901, Line 5, Col 26, Line 5, Col 27, "This type definition involves a cyclic reference through a spread." - Warning 3897, Line 4, Col 41, Line 4, Col 45, "Spread field 'A: int' from type 'S' shadows an explicitly declared field with the same name." ] [] @@ -946,7 +943,6 @@ but here has type Error 3901, Line 9, Col 34, Line 9, Col 35, "This type definition involves a cyclic reference through a spread." Error 3901, Line 11, Col 26, Line 11, Col 27, "This type definition involves a cyclic reference through a spread." Error 3901, Line 17, Col 34, Line 17, Col 35, "This type definition involves a cyclic reference through a spread." - Warning 3897, Line 6, Col 45, Line 6, Col 51, "Spread field 'A: int' from type 'O.S' shadows an explicitly declared field with the same name." ] [] @@ -1063,8 +1059,8 @@ but here has type |> typecheck |> shouldFail |> withDiagnostics [ - Error 37, Line 4, Col 56, Line 4, Col 57, "Duplicate definition of field 'δ'" Warning 3897, Line 4, Col 41, Line 4, Col 45, "Spread field 'α: int' from type 'S' shadows an explicitly declared field with the same name." + Error 37, Line 4, Col 56, Line 4, Col 57, "Duplicate definition of field 'δ'" ] module Nullability = diff --git a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs index 1470470c96d..213e8912ced 100644 --- a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs @@ -1856,9 +1856,7 @@ let ``Test project 8 all symbols`` () = allUsesOfAllSymbols |> shouldEqual - [|("int", "int", "file1", ((4, 19), (4, 22)), ["type"], ["abbrev"]); - ("int", "int", "file1", ((4, 31), (4, 34)), ["type"], ["abbrev"]); - ("field xxx", "xxx", "file1", ((4, 14), (4, 17)), ["defn"], ["field"]); + [|("field xxx", "xxx", "file1", ((4, 14), (4, 17)), ["defn"], ["field"]); ("int", "int", "file1", ((4, 19), (4, 22)), ["type"], ["abbrev"]); ("field yyy", "yyy", "file1", ((4, 25), (4, 28)), ["defn"], ["field"]); ("int", "int", "file1", ((4, 31), (4, 34)), ["type"], ["abbrev"]); @@ -2393,7 +2391,6 @@ let ``Test Project14 all symbols`` () = ("StructAttribute", "StructAttribute", "file1", ((4, 2), (4, 8)), ["attribute"]); ("member .ctor", "StructAttribute", "file1", ((4, 2), (4, 8)), []); ("int", "int", "file1", ((5, 9), (5, 12)), ["type"]); - ("int", "int", "file1", ((5, 9), (5, 12)), ["type"]); ("S", "S", "file1", ((5, 5), (5, 6)), ["defn"]); ("int", "int", "file1", ((5, 9), (5, 12)), ["type"]); ("val p", "p", "file1", ((5, 7), (5, 8)), ["defn"]); @@ -2551,9 +2548,6 @@ let ``Test Project16 all symbols`` () = ("member .ctor", "ClassAttribute", "sig1", ((12, 6), (12, 11)), [], ["member"]); ("int", "int", "sig1", ((16, 19), (16, 22)), ["type"], ["abbrev"]); ("int", "int", "sig1", ((16, 33), (16, 36)), ["type"], ["abbrev"]); - ("int", "int", "sig1", ((17, 25), (17, 28)), ["type"], ["abbrev"]); - ("int", "int", "sig1", ((16, 19), (16, 22)), ["type"], ["abbrev"]); - ("int", "int", "sig1", ((16, 33), (16, 36)), ["type"], ["abbrev"]); ("field Field1", "Field1", "sig1", ((16, 10), (16, 16)), ["defn"], ["field"]); ("field Field2", "Field2", "sig1", ((16, 24), (16, 30)), ["defn"], @@ -2590,9 +2584,6 @@ let ``Test Project16 all symbols`` () = ("Impl", "Impl", "sig1", ((2, 7), (2, 11)), ["defn"], ["module"]); ("int", "int", "file1", ((13, 19), (13, 22)), ["type"], ["abbrev"]); ("int", "int", "file1", ((13, 33), (13, 36)), ["type"], ["abbrev"]); - ("int", "int", "file1", ((14, 25), (14, 28)), ["type"], ["abbrev"]); - ("int", "int", "file1", ((13, 19), (13, 22)), ["type"], ["abbrev"]); - ("int", "int", "file1", ((13, 33), (13, 36)), ["type"], ["abbrev"]); ("field Field1", "Field1", "file1", ((13, 10), (13, 16)), ["defn"], ["field"]); ("field Field2", "Field2", "file1", ((13, 24), (13, 30)), ["defn"], @@ -5213,7 +5204,6 @@ let ``Test Project40 all symbols`` () = ("CompilationRepresentationAttribute", ((6, 2), (6, 27)), ["member"]); ("CompilationRepresentationFlags", ((6, 28), (6, 58)), ["enum"; "valuetype"]); ("UseNullAsTrueValue", ((6, 28), (6, 77)), ["field"; "static"; "8"]); - ("string", ((9, 11), (9, 17)), ["abbrev"]); ("string", ((9, 11), (9, 17)), ["abbrev"]); ("A", ((8, 6), (8, 7)), []); ("B", ((9, 6), (9, 7)), []); ("C", ((7, 5), (7, 6)), ["union"]); ("IsItAnA", ((10, 13), (10, 20)), ["member"; "getter"; "funky"]); diff --git a/tests/FSharp.Compiler.Service.Tests/Symbols.fs b/tests/FSharp.Compiler.Service.Tests/Symbols.fs index 499038ab0a7..87a66a9aa58 100644 --- a/tests/FSharp.Compiler.Service.Tests/Symbols.fs +++ b/tests/FSharp.Compiler.Service.Tests/Symbols.fs @@ -1904,6 +1904,83 @@ let r2 = {| ...r1; C = 3 |} if getRangeCoords su.Range <> getRangeCoords spreadUse.Range then failwith $"GetSymbolUseAtLocation range %A{getRangeCoords su.Range} should match GetUsesOfSymbolInFile range %A{getRangeCoords spreadUse.Range} (no leading '...')." + let private symbolUses (checkResults: FSharpCheckFileResults) = + [ for su in checkResults.GetAllUsesOfAllSymbolsInFile() -> string su.Symbol, tups su.Range, attribsOfSymbolUse su ] + + [] + let ``Record spread 01 - Type definition`` () = + let _, checkResults = + getParseAndCheckResultsPreview """ +type R1 = { A: int } +type R2 = { ...R1; B: int } +let r = { A = 1; B = 2 } +let _ = { r with A = 1 } +""" + symbolUses checkResults + |> shouldEqual + [ "int", ((2, 15), (2, 18)), [ "type" ] + "field A", ((2, 12), (2, 13)), [ "defn" ] + "R1", ((2, 5), (2, 7)), [ "defn" ] + "R1", ((3, 15), (3, 17)), [ "type" ] + "int", ((3, 22), (3, 25)), [ "type" ] + "field B", ((3, 19), (3, 20)), [ "defn" ] + "R2", ((3, 5), (3, 7)), [ "defn" ] + "field A", ((4, 10), (4, 11)), [] + "field B", ((4, 17), (4, 18)), [] + "val r", ((4, 4), (4, 5)), [ "defn" ] + "val r", ((5, 10), (5, 11)), [] + "field A", ((5, 17), (5, 18)), [] + "Test", ((1, 0), (1, 0)), [ "defn" ] ] + + let spreadField = checkResults |> findSymbolUse (fun su -> su.Range.StartLine = 5 && string su.Symbol = "field A") + let declaration = spreadField.Symbol.DeclarationLocation |> Option.get + tups declaration |> shouldEqual ((2, 12), (2, 13)) + declaration.IsSynthetic |> shouldEqual true + + [] + let ``Record spread 02 - Expression`` () = + let _, checkResults = + getParseAndCheckResultsPreview """ +type R1 = { A: int; B: int } +let r = { A = 1; B = 2 } +let _ = { ...r; B = 2 } +""" + symbolUses checkResults + |> shouldEqual + [ "int", ((2, 15), (2, 18)), [ "type" ] + "int", ((2, 23), (2, 26)), [ "type" ] + "field A", ((2, 12), (2, 13)), [ "defn" ] + "field B", ((2, 20), (2, 21)), [ "defn" ] + "R1", ((2, 5), (2, 7)), [ "defn" ] + "field A", ((3, 10), (3, 11)), [] + "field B", ((3, 17), (3, 18)), [] + "val r", ((3, 4), (3, 5)), [ "defn" ] + "val r", ((4, 13), (4, 14)), [] + "field B", ((4, 16), (4, 17)), [] + "Test", ((1, 0), (1, 0)), [ "defn" ] ] + + [] + let ``Record spread 03 - Anonymous record expression`` () = + let _, checkResults = + getParseAndCheckResultsPreview """ +type R1 = { A: int; B: int } +let r = { A = 1; B = 2 } +let _ = {| ...r; B = 2 |} +""" + symbolUses checkResults + |> shouldEqual + [ "int", ((2, 15), (2, 18)), [ "type" ] + "int", ((2, 23), (2, 26)), [ "type" ] + "field A", ((2, 12), (2, 13)), [ "defn" ] + "field B", ((2, 20), (2, 21)), [ "defn" ] + "R1", ((2, 5), (2, 7)), [ "defn" ] + "field A", ((3, 10), (3, 11)), [] + "field B", ((3, 17), (3, 18)), [] + "val r", ((3, 4), (3, 5)), [ "defn" ] + "val r", ((4, 14), (4, 15)), [] + "field B", ((4, 17), (4, 18)), [] + "Test", ((1, 0), (1, 0)), [ "defn" ] ] + module FileSignature = open FSharp.Compiler.NameResolution From ac769822dbbe2a6e332880c26e2dbd60f9d57ea8 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Mon, 26 Oct 2026 19:13:43 +0100 Subject: [PATCH 2/3] Fix Co-Authored-By: Claude Opus 5.5 --- tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs index 213e8912ced..240e9bfc9c2 100644 --- a/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ProjectAnalysisTests.fs @@ -3766,7 +3766,6 @@ let ``Test Project25 symbol uses of type-provided members`` () = ("ErasedWithConstructor.Provided.MyType", "file1", ((5, 8), (5, 9)), [ "member" ]) // line 5: let _ = >T<() (ctor) ("ErasedWithConstructor.Provided.MyType.DoNothing", "file1", ((5, 8), (5, 21)), [ "member" ]) // line 5: let _ = >T().DoNothing<() ("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), [ "abbrev" ]) // line 7: type Record = { Field: >int< } - ("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), [ "abbrev" ]) // (repeated) ("TypeProviderTests.Record.Field", "file1", ((7, 16), (7, 21)), [ "field" ]) // line 7: type Record = { >Field<: int } ("TypeProviderTests.Record", "file1", ((7, 5), (7, 11)), [ "record" ]) // line 7: type >Record< = ... ("TypeProviderTests.Record", "file1", ((8, 10), (8, 16)), [ "record" ]) // line 8: let r = { >Record<.Field = 1 } From 5ea66a678b652d16df54df613fe5dcc78c4426c4 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 27 Oct 2026 16:20:32 +0100 Subject: [PATCH 3/3] Release notes --- docs/release-notes/.FSharp.Compiler.Service/11.0.200.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md index 405f8ee25c1..9283cff0be8 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md @@ -31,7 +31,7 @@ * Preserve SRTP extension scopes across source files and competing witnesses through imported inline calls. Compile generic extension forwarding, including quotations, in unoptimized builds. Preserve applicable built-in operator priority in imported inline quotations. Do not reuse SRTP specializations across different selected witnesses. ([Issue #20683](https://github.com/dotnet/fsharp/issues/20683), [Issue #20684](https://github.com/dotnet/fsharp/issues/20684), [Issue #20685](https://github.com/dotnet/fsharp/issues/20685), [PR #20717](https://github.com/dotnet/fsharp/pull/20717)) * Transparent compiler: with `enablePartialTypeChecking`, a project that is only referenced by another project is type checked from its signatures. Implementation files that have a signature file are no longer parsed or type checked to produce the referenced assembly. `ParseAndCheckProject` still checks all files. ([PR #20703](https://github.com/dotnet/fsharp/pull/20703)) * With `enablePartialTypeChecking`, a project that is only referenced by another project no longer type checks its implementation files that have a signature file. Producing the referenced assembly only needs the signatures, but finalizing the project also collected the diagnostics of the skipped files, which type checked all of them and kept their results in memory. `ParseAndCheckProject` still reports the diagnostics of these files. ([PR #20699](https://github.com/dotnet/fsharp/pull/20699)) -* Checker: check record spreads once +* Checker: check record spreads once ([PR #20867](https://github.com/dotnet/fsharp/pull/20867)) ### Changed