Skip to content

Commit 5d9f168

Browse files
committed
Unify anonymous record fields
1 parent 9fadb2e commit 5d9f168

35 files changed

+810
-546
lines changed

src/Compiler/Checking/CheckRecordSyntaxHelpers.fs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
100100
)
101101
| _ -> None
102102

103-
let rec synExprRecd copyInfo (outerFieldId: Ident) innerFields exprBeingAssigned =
103+
let rec synExprRecd copyInfo (outerFieldId: Ident) innerFields (exprBeingAssigned: SynExpr) =
104104
match innerFields with
105105
| [] -> failwith "unreachable"
106106
| (fieldId: Ident, item) :: rest ->
@@ -115,11 +115,13 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
115115
synExprRecd copyInfo fieldId rest exprBeingAssigned
116116

117117
match item with
118-
| Item.AnonRecdField(
119-
anonInfo = {
120-
AnonRecdTypeInfo.TupInfo = TupInfo.Const isStruct
121-
}) ->
122-
let fields = [ LongIdentWithDots([ fieldId ], []), None, nestedField ]
118+
| Item.AnonRecdField(anonInfo = { TupInfo = TupInfo.Const isStruct }) ->
119+
let fieldName = (SynLongIdent([ fieldId ], [], [ None ]), true)
120+
let fieldRange = unionRanges fieldId.idRange nestedField.Range
121+
122+
let fields =
123+
[ SynExprRecordField(fieldName, None, Some nestedField, fieldRange, None) ]
124+
123125
SynExpr.AnonRecd(isStruct, copyInfo outerFieldId, fields, outerFieldId.idRange, { OpeningBraceRange = range0 })
124126
| _ ->
125127
let fields =

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7855,42 +7855,69 @@ and CheckAnonRecdExprDuplicateFields (elems: Ident array) =
78557855
errorR(Error (FSComp.SR.tcAnonRecdDuplicateFieldId(uc1.idText), uc1.idRange))))
78567856

78577857
// Check '{| .... |}'
7858-
and TcAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, optOrigSynExpr, unsortedFieldIdsAndSynExprsGiven, mWholeExpr) =
7858+
and TcAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, optOrigSynExpr, recordFields: SynExprRecordField list, mWholeExpr) =
78597859
match optOrigSynExpr with
78607860
| None ->
7861-
TcNewAnonRecdExpr cenv overallTy env tpenv (isStruct, unsortedFieldIdsAndSynExprsGiven, mWholeExpr)
7861+
TcNewAnonRecdExpr cenv overallTy env tpenv (isStruct, recordFields, mWholeExpr)
78627862

78637863
| Some orig ->
78647864
// Ideally we should also check for duplicate field IDs in the TcCopyAndUpdateAnonRecdExpr case, but currently the logic is too complex to guarantee a proper error reporting
78657865
// So here we error instead errorR to avoid cascading internal errors
7866-
unsortedFieldIdsAndSynExprsGiven
7867-
|> List.countBy (fun (fId, _, _) -> textOfLid fId.LongIdent)
7866+
recordFields
7867+
|> List.countBy (fun (SynExprRecordField(fieldName = (fId, _))) -> textOfLid fId.LongIdent)
78687868
|> List.iter (fun (label, count) ->
78697869
if count > 1 then error (Error (FSComp.SR.tcAnonRecdDuplicateFieldId(label), mWholeExpr)))
78707870

7871-
TcCopyAndUpdateAnonRecdExpr cenv overallTy env tpenv (isStruct, orig, unsortedFieldIdsAndSynExprsGiven, mWholeExpr)
7871+
TcCopyAndUpdateAnonRecdExpr cenv overallTy env tpenv (isStruct, orig, recordFields, mWholeExpr)
78727872

7873-
and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedFieldIdsAndSynExprsGiven, mWholeExpr) =
7873+
and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, recordFields: SynExprRecordField list, mWholeExpr) =
78747874

78757875
let g = cenv.g
7876-
let unsortedFieldSynExprsGiven = unsortedFieldIdsAndSynExprsGiven |> List.map (fun (_, _, fieldExpr) -> fieldExpr)
7877-
let unsortedFieldIds = unsortedFieldIdsAndSynExprsGiven |> List.map (fun (synLongIdent, _, _) -> synLongIdent.LongIdent[0]) |> List.toArray
7878-
let anonInfo, sortedFieldTys = UnifyAnonRecdTypeAndInferCharacteristics env.eContextInfo cenv env.DisplayEnv mWholeExpr overallTy isStruct unsortedFieldIds
7876+
7877+
// For new anonymous records, each field label must be a single identifier.
7878+
// If a long identifier is given, report an error and skip that entry for typechecking purposes.
7879+
// Also normalize missing expressions to an error expression to keep traversal stable.
7880+
let validFields : (Ident * SynExpr * SynLongIdent) list =
7881+
recordFields
7882+
|> List.choose (fun (SynExprRecordField(fieldName = (synLongIdent, _); expr = e; range = mField)) ->
7883+
match synLongIdent.LongIdent with
7884+
| [ id ] ->
7885+
let exprGiven =
7886+
match e with
7887+
| Some e -> e
7888+
| None -> SynExpr.FromParseError(SynExpr.Const(SynConst.Unit, mField), mField)
7889+
Some(id, exprGiven, synLongIdent)
7890+
| [] ->
7891+
// Should not occur for anonymous record expressions; silently skip with a diagnostic.
7892+
errorR (Error(FSComp.SR.parsInvalidAnonRecdType(), synLongIdent.Range))
7893+
None
7894+
| _ ->
7895+
// Nested labels are only valid in copy-and-update; not allowed for new anonymous records
7896+
errorR (Error(FSComp.SR.parsInvalidAnonRecdType(), synLongIdent.Range))
7897+
None)
7898+
7899+
let unsortedFieldSynExprsGiven = validFields |> List.map (fun (_, e, _) -> e)
7900+
7901+
let unsortedFieldIds = validFields |> List.map (fun (id, _, _) -> id) |> List.toArray
7902+
7903+
let anonInfo, sortedFieldTys =
7904+
UnifyAnonRecdTypeAndInferCharacteristics env.eContextInfo cenv env.DisplayEnv mWholeExpr overallTy isStruct unsortedFieldIds
78797905

78807906
if unsortedFieldIds.Length > 1 then
78817907
CheckAnonRecdExprDuplicateFields unsortedFieldIds
78827908

78837909
// Sort into canonical order
78847910
let sortedIndexedArgs =
7885-
unsortedFieldIdsAndSynExprsGiven
7911+
validFields
78867912
|> List.indexed
78877913
|> List.sortBy (fun (i,_) -> unsortedFieldIds[i].idText)
78887914

78897915
// Map from sorted indexes to unsorted indexes
78907916
let sigma = sortedIndexedArgs |> List.map fst |> List.toArray
7891-
let sortedFieldExprs = sortedIndexedArgs |> List.map snd
7917+
let sortedFieldTriples = sortedIndexedArgs |> List.map snd
78927918

7893-
sortedFieldExprs |> List.iteri (fun j (synLongIdent, _, _) ->
7919+
sortedFieldTriples
7920+
|> List.iteri (fun j (_, _, synLongIdent) ->
78947921
let m = rangeOfLid synLongIdent.LongIdent
78957922
let item = Item.AnonRecdField(anonInfo, sortedFieldTys, j, m)
78967923
CallNameResolutionSink cenv.tcSink (m, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights))
@@ -7903,11 +7930,12 @@ and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedField
79037930

79047931
let flexes = unsortedFieldTys |> List.map (fun _ -> true)
79057932

7906-
let unsortedCheckedArgs, tpenv = TcExprsWithFlexes cenv env mWholeExpr tpenv flexes unsortedFieldTys unsortedFieldSynExprsGiven
7933+
let unsortedCheckedArgs, tpenv =
7934+
TcExprsWithFlexes cenv env mWholeExpr tpenv flexes unsortedFieldTys unsortedFieldSynExprsGiven
79077935

79087936
mkAnonRecd g mWholeExpr anonInfo unsortedFieldIds unsortedCheckedArgs unsortedFieldTys, tpenv
79097937

7910-
and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (origExpr, blockSeparator), unsortedFieldIdsAndSynExprsGiven, mWholeExpr) =
7938+
and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (origExpr, blockSeparator), recordFields: SynExprRecordField list, mWholeExpr) =
79117939
// The fairly complex case '{| origExpr with X = 1; Y = 2 |}'
79127940
// The origExpr may be either a record or anonymous record.
79137941
// The origExpr may be either a struct or not.
@@ -7926,14 +7954,17 @@ and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (or
79267954
if not (isAppTy g origExprTy || isAnonRecdTy g origExprTy) then
79277955
error (Error (FSComp.SR.tcCopyAndUpdateNeedsRecordType(), mOrigExpr))
79287956

7929-
// Expand expressions with respect to potential nesting
7957+
// Expand expressions with respect to potentially nesting
79307958
let unsortedFieldIdsAndSynExprsGiven =
7931-
unsortedFieldIdsAndSynExprsGiven
7932-
|> List.map (fun (synLongIdent, _, exprBeingAssigned) ->
7959+
recordFields
7960+
|> List.map (fun (SynExprRecordField(fieldName = (synLongIdent, _); expr = e)) ->
79337961
match synLongIdent.LongIdent with
79347962
| [] -> error(Error(FSComp.SR.nrUnexpectedEmptyLongId(), mWholeExpr))
7935-
| [ id ] -> ([], id), Some exprBeingAssigned
7936-
| lid -> TransformAstForNestedUpdates cenv env origExprTy lid exprBeingAssigned (origExpr, blockSeparator))
7963+
| [ id ] -> ([], id), e
7964+
| lid ->
7965+
match e with
7966+
| Some exprBeingAssigned -> TransformAstForNestedUpdates cenv env origExprTy lid exprBeingAssigned (origExpr, blockSeparator)
7967+
| None -> List.frontAndBack lid, None)
79377968
|> GroupUpdatesToNestedFields
79387969

79397970
let unsortedFieldSynExprsGiven = unsortedFieldIdsAndSynExprsGiven |> List.choose snd

src/Compiler/Driver/GraphChecking/FileContentMapping.fs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,15 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
378378
| SynExpr.AnonRecd(copyInfo = copyInfo; recordFields = recordFields) ->
379379
let continuations =
380380
match copyInfo with
381-
| None -> List.map (fun (_, _, e) -> visit e) recordFields
382-
| Some(cp, _) -> visit cp :: List.map (fun (_, _, e) -> visit e) recordFields
381+
| None ->
382+
recordFields
383+
|> List.choose (fun (SynExprRecordField(expr = e)) -> e)
384+
|> List.map visit
385+
| Some(cp, _) ->
386+
visit cp
387+
:: (recordFields
388+
|> List.choose (fun (SynExprRecordField(expr = e)) -> e)
389+
|> List.map visit)
383390

384391
Continuation.concatenate continuations continuation
385392
| SynExpr.ArrayOrList(exprs = exprs) ->

src/Compiler/Service/FSharpParseFileResults.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
641641
| Some(e, _) -> yield! walkExpr true e
642642
| None -> ()
643643

644-
yield! walkExprs (fs |> List.map (fun (_, _, e) -> e))
644+
yield! walkExprs (fs |> List.choose (fun (SynExprRecordField(expr = e)) -> e))
645645

646646
| SynExpr.ObjExpr(argOptions = args; bindings = bs; members = ms; extraImpls = is) ->
647647
let bs = unionBindingAndMembers bs ms

src/Compiler/Service/ServiceParseTreeWalk.fs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,42 @@ module SyntaxTraversal =
458458
None)
459459
| _ -> ()
460460

461-
for field, _, x in fields do
462-
yield dive () field.Range (fun () -> visitor.VisitRecordField(path, copyOpt |> Option.map fst, Some field))
463-
yield dive x x.Range traverseSynExpr
461+
let copyOpt = Option.map fst copyOpt
462+
463+
for SynExprRecordField(fieldName = (field, _); expr = e; blockSeparator = sepOpt) in fields do
464+
yield
465+
dive (path, copyOpt, Some field) field.Range (fun r ->
466+
if rangeContainsPos field.Range pos then
467+
visitor.VisitRecordField r
468+
else
469+
None)
470+
471+
match e with
472+
| Some e ->
473+
yield
474+
dive e e.Range (fun inner ->
475+
// special case: caret is below field binding
476+
// field x = 5
477+
// $
478+
if
479+
not (rangeContainsPos e.Range pos)
480+
&& sepOpt.IsNone
481+
&& pos.Column = field.Range.StartColumn
482+
then
483+
visitor.VisitRecordField(path, copyOpt, None)
484+
else
485+
traverseSynExpr inner)
486+
| None -> ()
487+
488+
match sepOpt with
489+
| Some blockSep ->
490+
yield
491+
dive () blockSep.Range (fun () ->
492+
if posGeq pos blockSep.Range.End then
493+
visitor.VisitRecordField(path, copyOpt, None)
494+
else
495+
None)
496+
| _ -> ()
464497
]
465498
|> pick expr
466499

src/Compiler/Service/SynExpr.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,8 +1121,8 @@ module SynExpr =
11211121
let rec loop recordFields =
11221122
match recordFields with
11231123
| [] -> false
1124-
| (_, Some _blockSeparator, SynExpr.Paren(expr = Is inner)) :: (SynLongIdent(id = id :: _), _, _) :: _ ->
1125-
problematic inner.Range id.idRange
1124+
| SynExprRecordField(expr = Some(SynExpr.Paren(expr = Is inner)); blockSeparator = Some _) :: SynExprRecordField(
1125+
fieldName = SynLongIdent(id = id :: _), _) :: _ -> problematic inner.Range id.idRange
11261126
| _ :: recordFields -> loop recordFields
11271127

11281128
loop recordFields

src/Compiler/SyntaxTree/SyntaxTree.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ type SynExpr =
550550
| AnonRecd of
551551
isStruct: bool *
552552
copyInfo: (SynExpr * BlockSeparator) option *
553-
recordFields: (SynLongIdent * range option * SynExpr) list *
553+
recordFields: SynExprRecordField list *
554554
range: range *
555555
trivia: SynExprAnonRecdTrivia
556556

src/Compiler/SyntaxTree/SyntaxTree.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ type SynExpr =
608608
| AnonRecd of
609609
isStruct: bool *
610610
copyInfo: (SynExpr * BlockSeparator) option *
611-
recordFields: (SynLongIdent * range option * SynExpr) list *
611+
recordFields: SynExprRecordField list *
612612
range: range *
613613
trivia: SynExprAnonRecdTrivia
614614

src/Compiler/SyntaxTree/SyntaxTreeOps.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -915,11 +915,12 @@ let rec synExprContainsError inpExpr =
915915
| SynExpr.ArrayOrList(_, es, _)
916916
| SynExpr.Tuple(_, es, _, _) -> walkExprs es
917917

918-
| SynExpr.AnonRecd(copyInfo = origExpr; recordFields = flds) ->
918+
| SynExpr.AnonRecd(copyInfo = origExpr; recordFields = fs) ->
919919
(match origExpr with
920920
| Some(e, _) -> walkExpr e
921921
| None -> false)
922-
|| walkExprs (List.map (fun (_, _, e) -> e) flds)
922+
|| (let flds = fs |> List.choose (fun (SynExprRecordField(expr = v)) -> v)
923+
walkExprs flds)
923924

924925
| SynExpr.Record(_, origExpr, fs, _) ->
925926
(match origExpr with

src/Compiler/pars.fsy

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5912,12 +5912,6 @@ braceBarExpr:
59125912
braceBarExprCore:
59135913
| LBRACE_BAR recdExprCore bar_rbrace
59145914
{ let orig, flds = $2
5915-
let flds =
5916-
flds |> List.choose (function
5917-
| SynExprRecordField((synLongIdent, _), mEquals, Some e, _, _) when orig.IsSome -> Some(synLongIdent, mEquals, e) // copy-and-update, long identifier signifies nesting
5918-
| SynExprRecordField((SynLongIdent([ _id ], _, _) as synLongIdent, _), mEquals, Some e, _, _) -> Some(synLongIdent, mEquals, e) // record construction, long identifier not valid
5919-
| SynExprRecordField((synLongIdent, _), mEquals, None, _, _) -> Some(synLongIdent, mEquals, arbExpr ("anonField", synLongIdent.Range))
5920-
| _ -> reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsInvalidAnonRecdType()); None)
59215915
let mLeftBrace = rhs parseState 1
59225916
let mRightBrace = rhs parseState 3
59235917
(fun (mStruct: range option) ->
@@ -5927,10 +5921,6 @@ braceBarExprCore:
59275921
| LBRACE_BAR recdExprCore recover
59285922
{ reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedBraceBar())
59295923
let orig, flds = $2
5930-
let flds =
5931-
flds |> List.map (function
5932-
| SynExprRecordField((synLongIdent, _), mEquals, Some e, _, _) -> (synLongIdent, mEquals, e)
5933-
| SynExprRecordField((synLongIdent, _), mEquals, None, _, _) -> (synLongIdent, mEquals, arbExpr ("anonField", synLongIdent.Range)))
59345924
let mLeftBrace = rhs parseState 1
59355925
let mExpr = rhs parseState 2
59365926
(fun (mStruct: range option) ->

0 commit comments

Comments
 (0)