Skip to content

Commit f90deb2

Browse files
It works, but it sure ain't pretty
1 parent 371b7e9 commit f90deb2

File tree

2 files changed

+116
-12
lines changed

2 files changed

+116
-12
lines changed

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8065,7 +8065,93 @@ and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedField
80658065

80668066
let maybeAnonRecdTargetTy = tryDestAnonRecdTy g overallTy
80678067

8068+
let possibleTargetTyAt =
8069+
match maybeAnonRecdTargetTy with
8070+
| ValueSome (anonInfo, tys) ->
8071+
let names = anonInfo.SortedNames
8072+
let tys = List.toArray tys
8073+
fun name ->
8074+
let i = Array.BinarySearch (names, name)
8075+
if i < 0 then ValueNone
8076+
else ValueSome tys[i]
8077+
| ValueNone -> fun _ -> ValueNone
8078+
80688079
let spreadSrcs, unsortedCheckedFields, sortedCheckedFields, tpenv =
8080+
let (|LeftwardExplicit|NoLeftwardExplicit|) hasLeftwardExplicit = if hasLeftwardExplicit then LeftwardExplicit else NoLeftwardExplicit
8081+
let LeftwardExplicit = true
8082+
let NoLeftwardExplicit = false
8083+
8084+
let rec mkTcFuncs tys i fieldsAndSpreads =
8085+
match fieldsAndSpreads with
8086+
| [] ->
8087+
let acc =
8088+
(Map.empty, tys) ||> Map.fold (fun acc _ (_, tys) ->
8089+
let (i, ty), tys = List.headAndTail tys
8090+
let acc = acc |> Map.add i (fun _ expr tpenv -> ty, TcExprFlex cenv true false ty env tpenv expr)
8091+
(acc, tys) ||> List.fold (fun acc (i, ty) -> acc |> Map.add i (fun m _ tpenv -> ty, (mkThrow m ty (mkOne g m), tpenv))))
8092+
8093+
fun i m expr tpenv ->
8094+
acc
8095+
|> Map.tryFind i
8096+
|> Option.map (fun tc -> tc m expr tpenv)
8097+
|> Option.defaultWith (fun () -> g.obj_ty_ambivalent, (mkThrow m g.obj_ty_ambivalent (mkOne g m), tpenv))
8098+
8099+
| SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (fieldName = SynLongIdent (([] | _ :: _ :: _), _, _); range = m), _) :: _ ->
8100+
error (InternalError ("All field names should have been transformed into simple identifiers by this point.", m))
8101+
8102+
// Explicitly redeclared fields are not allowed:
8103+
// {| A = 3; A = 4 |}
8104+
// ↑ error FS3522
8105+
| SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (fieldName = SynLongIdent ([fieldId], _, _)), _) :: fieldsAndSpreads ->
8106+
let ty = possibleTargetTyAt fieldId.idText |> ValueOption.defaultWith (fun () -> NewInferenceType g)
8107+
8108+
let tys =
8109+
tys |> Map.change fieldId.idText (function
8110+
| None -> Some (LeftwardExplicit, [i, ty])
8111+
| Some (LeftwardExplicit, dupes) -> Some (LeftwardExplicit, (i, ty) :: dupes)
8112+
| Some (NoLeftwardExplicit, _dupes) -> Some (LeftwardExplicit, [i, ty]))
8113+
8114+
mkTcFuncs tys (i + 1) fieldsAndSpreads
8115+
8116+
// Field shadowing from spreads is allowed:
8117+
// let a = {| A = 3 |}
8118+
// let b = {| A = "4" |}
8119+
// let c = {| ...a; ...b |} → {| A = "4" |}
8120+
| SynExprAnonRecordFieldOrSpread.Spread (SynExprSpread (expr = expr; range = m), _) :: fieldsAndSpreads ->
8121+
checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RecordSpreads m
8122+
8123+
let flex = false
8124+
let spreadSrcExpr, _ = TcExprFlex cenv flex false (NewInferenceType g) env tpenv expr
8125+
let tyOfSpreadSrcExpr = tyOfExpr g spreadSrcExpr
8126+
8127+
let fieldsFromSpread =
8128+
if isRecdTy g tyOfSpreadSrcExpr then
8129+
ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad tyOfSpreadSrcExpr false
8130+
|> List.choose (function
8131+
| Item.RecdField field ->
8132+
let fieldId = field.RecdField.Id.idText
8133+
Some (fieldId, possibleTargetTyAt fieldId |> ValueOption.defaultValue field.FieldType)
8134+
| _ -> None)
8135+
else
8136+
match tryDestAnonRecdTy g tyOfSpreadSrcExpr with
8137+
| ValueSome (anonInfo, tys) ->
8138+
tys |> List.mapi (fun j ty ->
8139+
let fieldId = anonInfo.SortedNames[j]
8140+
fieldId, possibleTargetTyAt fieldId |> ValueOption.defaultValue ty)
8141+
| ValueNone -> []
8142+
8143+
let i, tys =
8144+
((i, tys), fieldsFromSpread)
8145+
||> List.fold (fun (i, tys) (fieldId, ty) ->
8146+
i + 1, tys |> Map.change fieldId (function
8147+
| None -> Some (NoLeftwardExplicit, [i, ty])
8148+
| Some (LeftwardExplicit, _dupes) -> Some (LeftwardExplicit, [i, ty])
8149+
| Some (NoLeftwardExplicit, _dupes) -> Some (NoLeftwardExplicit, [i, ty])))
8150+
8151+
mkTcFuncs tys i fieldsAndSpreads
8152+
8153+
let targetTys = mkTcFuncs Map.empty 0 unsortedFieldIdsAndSynExprsGiven
8154+
80698155
let rec tcFieldsAndSpreads spreadSrcs checkedFields i tpenv fieldsAndSpreads =
80708156
let (|LeftwardExplicit|NoLeftwardExplicit|) hasLeftwardExplicit = if hasLeftwardExplicit then LeftwardExplicit else NoLeftwardExplicit
80718157
let LeftwardExplicit = true
@@ -8105,9 +8191,7 @@ and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedField
81058191
// {| A = 3; A = 4 |}
81068192
// ↑ error FS3522
81078193
| SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (fieldName = SynLongIdent ([fieldId], _, _); expr = expr; range = m), _) :: fieldsAndSpreads ->
8108-
let flex = true
8109-
let ty = NewInferenceType g
8110-
let expr, tpenv = TcExprFlex cenv flex false ty env tpenv expr
8194+
let ty, (expr, tpenv) = targetTys i m expr tpenv
81118195

81128196
let checkedFields =
81138197
checkedFields |> Map.change fieldId.idText (function
@@ -8164,7 +8248,7 @@ and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedField
81648248
| Item.RecdField fieldInfo :: fieldsFromSpread ->
81658249
let fieldExpr = mkRecdFieldGetViaExprAddr (spreadSrcAddrExpr, fieldInfo.RecdFieldRef, fieldInfo.TypeInst, m)
81668250
let fieldId = fieldInfo.RecdFieldRef.RecdField.Id
8167-
let ty = fieldInfo.FieldType
8251+
let ty = possibleTargetTyAt fieldId.idText |> ValueOption.defaultValue fieldInfo.FieldType
81688252

81698253
let checkedFields =
81708254
checkedFields |> Map.change fieldId.idText (function
@@ -8261,20 +8345,18 @@ and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedField
82618345
let anonInfo, sortedFieldTys =
82628346
let anonInfo, sortedFieldTys =
82638347
match maybeAnonRecdTargetTy with
8264-
| ValueSome (anonInfo, ptys) ->
8348+
| ValueSome (anonInfo, _) ->
82658349
// Note: use the assembly of the known type, not the current assembly
82668350
// Note: use the structness of the known type, unless explicit
82678351
// Note: use the names of our type, since they are always explicit
82688352
let tupInfo = if isStruct then tupInfoStruct else anonInfo.TupInfo
82698353
let anonInfo = AnonRecdTypeInfo.Create(anonInfo.Assembly, tupInfo, sortedNames)
82708354
let sortedFieldTys =
8271-
if List.length ptys = sortedNames.Length then ptys
8272-
else
8273-
[
8274-
for KeyValue (_, (_, dupes)) in sortedCheckedFields do
8275-
for _, _, ty, _ in dupes do
8276-
ty
8277-
]
8355+
[
8356+
for KeyValue (_, (_, dupes)) in sortedCheckedFields do
8357+
for _, _, ty, _ in dupes do
8358+
ty
8359+
]
82788360
anonInfo, sortedFieldTys
82798361
| ValueNone ->
82808362
// Note: no known anonymous record type - use our assembly

tests/FSharp.Compiler.ComponentTests/Language/SpreadTests.fs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,28 @@ module NominalAndAnonymousRecords =
11711171
|> compileExeAndRun
11721172
|> shouldSucceed
11731173

1174+
module BackCompat =
1175+
[<Fact>]
1176+
let ``Inference works the same`` () =
1177+
let src =
1178+
"""
1179+
let f x y =
1180+
if x = y then ()
1181+
else failwith $"Expected %A{x} = %A{y}."
1182+
1183+
f {| a = 1 - 1 |} {| a = Unchecked.defaultof<_> |}
1184+
1185+
#nowarn FS3883 // Spread shadowing explicit.
1186+
1187+
let r = {| a = Unchecked.defaultof<_> |}
1188+
f {| a = 1 - 1 |} {| a = "a"; ...r |}
1189+
"""
1190+
1191+
FSharp src
1192+
|> withLangVersion SupportedLangVersion
1193+
|> compileExeAndRun
1194+
|> shouldSucceed
1195+
11741196
module Conversions =
11751197
()
11761198

0 commit comments

Comments
 (0)