Skip to content

Commit 2ef33c4

Browse files
authored
Checker: recover on unknown record fields (#15214)
1 parent 024b98d commit 2ef33c4

File tree

8 files changed

+148
-23
lines changed

8 files changed

+148
-23
lines changed

src/Compiler/Checking/CheckExpressions.fs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,10 +1807,17 @@ let BuildFieldMap (cenv: cenv) env isPartial ty (flds: ((Ident list * Ident) * '
18071807
let fldResolutions =
18081808
let allFields = flds |> List.map (fun ((_, ident), _) -> ident)
18091809
flds
1810-
|> List.map (fun (fld, fldExpr) ->
1811-
let (fldPath, fldId) = fld
1812-
let frefSet = ResolveField cenv.tcSink cenv.nameResolver env.eNameResEnv ad ty fldPath fldId allFields
1813-
fld, frefSet, fldExpr)
1810+
|> List.choose (fun (fld, fldExpr) ->
1811+
try
1812+
let fldPath, fldId = fld
1813+
let frefSet = ResolveField cenv.tcSink cenv.nameResolver env.eNameResEnv ad ty fldPath fldId allFields
1814+
Some(fld, frefSet, fldExpr)
1815+
with e ->
1816+
errorRecoveryNoRange e
1817+
None
1818+
)
1819+
1820+
if fldResolutions.IsEmpty then None else
18141821

18151822
let relevantTypeSets =
18161823
fldResolutions |> List.map (fun (_, frefSet, _) ->
@@ -1870,7 +1877,7 @@ let BuildFieldMap (cenv: cenv) env isPartial ty (flds: ((Ident list * Ident) * '
18701877
Map.add fref2.FieldName fldExpr fs, (fref2.FieldName, fldExpr) :: rfldsList
18711878

18721879
| _ -> error(Error(FSComp.SR.tcRecordFieldInconsistentTypes(), m)))
1873-
tinst, tcref, fldsmap, List.rev rfldsList
1880+
Some(tinst, tcref, fldsmap, List.rev rfldsList)
18741881

18751882
let rec ApplyUnionCaseOrExn (makerForUnionCase, makerForExnTag) m (cenv: cenv) env overallTy item =
18761883
let g = cenv.g
@@ -7362,7 +7369,10 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
73627369
match flds with
73637370
| [] -> []
73647371
| _ ->
7365-
let tinst, tcref, _, fldsList = BuildFieldMap cenv env hasOrigExpr overallTy flds mWholeExpr
7372+
match BuildFieldMap cenv env hasOrigExpr overallTy flds mWholeExpr with
7373+
| None -> []
7374+
| Some(tinst, tcref, _, fldsList) ->
7375+
73667376
let gtyp = mkAppTy tcref tinst
73677377
UnifyTypes cenv env mWholeExpr overallTy gtyp
73687378

@@ -7393,7 +7403,7 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
73937403
error(Error(errorInfo, mWholeExpr))
73947404

73957405
if isFSharpObjModelTy g overallTy then errorR(Error(FSComp.SR.tcTypeIsNotARecordTypeNeedConstructor(), mWholeExpr))
7396-
elif not (isRecdTy g overallTy) then errorR(Error(FSComp.SR.tcTypeIsNotARecordType(), mWholeExpr))
7406+
elif not (isRecdTy g overallTy || fldsList.IsEmpty) then errorR(Error(FSComp.SR.tcTypeIsNotARecordType(), mWholeExpr))
73977407

73987408
let superInitExprOpt , tpenv =
73997409
match inherits, GetSuperTypeOfType g cenv.amap mWholeExpr overallTy with
@@ -7411,14 +7421,18 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
74117421
errorR(InternalError("Unexpected failure in getting super type", mWholeExpr))
74127422
None, tpenv
74137423

7414-
let expr, tpenv = TcRecordConstruction cenv overallTy env tpenv withExprInfoOpt overallTy fldsList mWholeExpr
7424+
if fldsList.IsEmpty && isTyparTy g overallTy then
7425+
SolveTypeAsError env.DisplayEnv cenv.css mWholeExpr overallTy
7426+
mkDefault (mWholeExpr, overallTy), tpenv
7427+
else
7428+
let expr, tpenv = TcRecordConstruction cenv overallTy env tpenv withExprInfoOpt overallTy fldsList mWholeExpr
74157429

7416-
let expr =
7417-
match superInitExprOpt with
7418-
| _ when isStructTy g overallTy -> expr
7419-
| Some superInitExpr -> mkCompGenSequential mWholeExpr superInitExpr expr
7420-
| None -> expr
7421-
expr, tpenv
7430+
let expr =
7431+
match superInitExprOpt with
7432+
| _ when isStructTy g overallTy -> expr
7433+
| Some superInitExpr -> mkCompGenSequential mWholeExpr superInitExpr expr
7434+
| None -> expr
7435+
expr, tpenv
74227436

74237437

74247438
// Check '{| .... |}'

src/Compiler/Checking/CheckExpressions.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ val BuildFieldMap:
895895
ty: TType ->
896896
flds: ((Ident list * Ident) * 'T) list ->
897897
m: range ->
898-
TypeInst * TyconRef * Map<string, 'T> * (string * 'T) list
898+
(TypeInst * TyconRef * Map<string, 'T> * (string * 'T) list) option
899899

900900
/// Check a long identifier 'Case' or 'Case argsR' that has been resolved to an active pattern case
901901
val TcPatLongIdentActivePatternCase:

src/Compiler/Checking/CheckPatterns.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,10 @@ and TcPatArrayOrList warnOnUpper cenv env vFlags patEnv ty isArray args m =
435435

436436
and TcRecordPat warnOnUpper cenv env vFlags patEnv ty fieldPats m =
437437
let fieldPats = fieldPats |> List.map (fun (fieldId, _, fieldPat) -> fieldId, fieldPat)
438-
let tinst, tcref, fldsmap, _fldsList = BuildFieldMap cenv env true ty fieldPats m
438+
match BuildFieldMap cenv env true ty fieldPats m with
439+
| None -> (fun _ -> TPat_error m), patEnv
440+
| Some(tinst, tcref, fldsmap, _fldsList) ->
441+
439442
let gtyp = mkAppTy tcref tinst
440443
let inst = List.zip (tcref.Typars m) tinst
441444

tests/FSharp.Compiler.ComponentTests/ErrorMessages/NameResolutionTests.fs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ let r:F = { Size=3; Height=4; Wall=1 }
2121
"""
2222
|> typecheck
2323
|> shouldFail
24-
|> withSingleDiagnostic (Error 1129, Line 9, Col 31, Line 9, Col 35,
25-
("The record type 'F' does not contain a label 'Wall'. Maybe you want one of the following:" + System.Environment.NewLine + " Wallis"))
24+
|> withDiagnostics [
25+
(Error 1129, Line 9, Col 31, Line 9, Col 35, "The record type 'F' does not contain a label 'Wall'. Maybe you want one of the following:" + System.Environment.NewLine + " Wallis")
26+
(Error 764, Line 9, Col 11, Line 9, Col 39, "No assignment given for field 'Wallis' of type 'Test.F'")
27+
]
2628

2729
[<Fact>]
2830
let RecordFieldProposal () =
@@ -38,5 +40,7 @@ let r = { Size=3; Height=4; Wall=1 }
3840
"""
3941
|> typecheck
4042
|> shouldFail
41-
|> withSingleDiagnostic (Error 39, Line 9, Col 29, Line 9, Col 33,
42-
("The record label 'Wall' is not defined. Maybe you want one of the following:" + System.Environment.NewLine + " Walls" + System.Environment.NewLine + " Wallis"))
43+
|> withDiagnostics [
44+
(Error 39, Line 9, Col 29, Line 9, Col 33, "The record label 'Wall' is not defined. Maybe you want one of the following:" + System.Environment.NewLine + " Walls" + System.Environment.NewLine + " Wallis")
45+
(Error 764, Line 9, Col 9, Line 9, Col 37, "No assignment given for field 'Wallis' of type 'Test.F'")
46+
]

tests/FSharp.Compiler.ComponentTests/ErrorMessages/SuggestionsTests.fs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,10 @@ let r = { Field1 = "hallo"; Field2 = 1 }
173173
"""
174174
|> typecheck
175175
|> shouldFail
176-
|> withSingleDiagnostic (Error 39, Line 8, Col 11, Line 8, Col 17,
177-
("The record label 'Field1' is not defined. Maybe you want one of the following:" + Environment.NewLine + " MyRecord.Field1"))
176+
|> withDiagnostics [
177+
(Error 39, Line 8, Col 11, Line 8, Col 17, "The record label 'Field1' is not defined. Maybe you want one of the following:" + Environment.NewLine + " MyRecord.Field1")
178+
(Error 39, Line 8, Col 29, Line 8, Col 35, "The record label 'Field2' is not defined. Maybe you want one of the following:" + Environment.NewLine + " MyRecord.Field2")
179+
]
178180

179181
[<Fact>]
180182
let ``Suggest Type Parameters`` () =

tests/fsharp/typecheck/sigs/neg07.bsl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,19 @@ neg07.fs(36,11,36,27): typecheck error FS0026: This rule will never be matched
2424
neg07.fs(46,15,46,27): typecheck error FS0039: The record label 'RecordLabel1' is not defined. Maybe you want one of the following:
2525
R.RecordLabel1
2626

27+
neg07.fs(46,33,46,45): typecheck error FS0039: The record label 'RecordLabel2' is not defined. Maybe you want one of the following:
28+
R.RecordLabel2
29+
30+
neg07.fs(47,17,47,55): typecheck error FS0025: Incomplete pattern matches on this expression.
31+
neg07.fs(47,59,47,60): typecheck error FS0039: The value or constructor 'a' is not defined.
32+
neg07.fs(47,63,47,64): typecheck error FS0039: The value or constructor 'b' is not defined.
33+
2734
neg07.fs(47,19,47,31): typecheck error FS0039: The record label 'RecordLabel1' is not defined. Maybe you want one of the following:
2835
R.RecordLabel1
2936

37+
neg07.fs(47,37,47,49): typecheck error FS0039: The record label 'RecordLabel2' is not defined. Maybe you want one of the following:
38+
R.RecordLabel2
39+
3040
neg07.fs(57,10,57,17): typecheck error FS1196: The 'UseNullAsTrueValue' attribute flag may only be used with union types that have one nullary case and at least one non-nullary case
3141

3242
neg07.fs(64,10,64,18): typecheck error FS1196: The 'UseNullAsTrueValue' attribute flag may only be used with union types that have one nullary case and at least one non-nullary case

tests/fsharpqa/Source/Conformance/InferenceProcedures/NameResolution/RequireQualifiedAccess/E_OnRecord.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Verify error when not fully qualifying a record field when it
33
// has the RequireQualifiedAccess attribute.
44

5-
//<Expects id="FS0039" status="error">The record label 'Field1' is not defined\.</Expects>
65
//<Expects id="FS0039" status="error">The record label 'Field1' is not defined\.</Expects>
76

87
[<RequireQualifiedAccess>]

tests/service/Symbols.fs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,96 @@ type Foo =
474474
(:? FSharpMemberOrFunctionOrValue as setMfv) ->
475475
Assert.AreNotEqual(getMfv.CurriedParameterGroups, setMfv.CurriedParameterGroups)
476476
| _ -> Assert.Fail "Expected symbols to be FSharpMemberOrFunctionOrValue"
477+
478+
module Expressions =
479+
[<Test>]
480+
let ``Unresolved record field 01`` () =
481+
let _, checkResults = getParseAndCheckResults """
482+
type R =
483+
{ F1: int
484+
F2: int }
485+
486+
{ F = 1
487+
F2 = 1 }
488+
"""
489+
getSymbolUses checkResults
490+
|> Seq.exists (fun symbolUse -> symbolUse.IsFromUse && symbolUse.Symbol.DisplayName = "F2")
491+
|> shouldEqual true
492+
493+
[<Test>]
494+
let ``Unresolved record field 02`` () =
495+
let _, checkResults = getParseAndCheckResults """
496+
[<RequireQualifiedAccess>]
497+
type R =
498+
{ F1: int
499+
F2: int }
500+
501+
{ F1 = 1
502+
R.F2 = 1 }
503+
"""
504+
getSymbolUses checkResults
505+
|> Seq.exists (fun symbolUse -> symbolUse.IsFromUse && symbolUse.Symbol.DisplayName = "F2")
506+
|> shouldEqual true
507+
508+
[<Test>]
509+
let ``Unresolved record field 03`` () =
510+
let _, checkResults = getParseAndCheckResults """
511+
[<RequireQualifiedAccess>]
512+
type R =
513+
{ F1: int
514+
F2: int }
515+
516+
{ R.F2 = 1
517+
F1 = 1 }
518+
"""
519+
getSymbolUses checkResults
520+
|> Seq.exists (fun symbolUse -> symbolUse.IsFromUse && symbolUse.Symbol.DisplayName = "F2")
521+
|> shouldEqual true
522+
523+
[<Test>]
524+
let ``Unresolved record field 04`` () =
525+
let _, checkResults = getParseAndCheckResults """
526+
type R =
527+
{ F1: int
528+
F2: int }
529+
530+
match Unchecked.defaultof<R> with
531+
{ F = 1
532+
F2 = 1 } -> ()
533+
"""
534+
getSymbolUses checkResults
535+
|> Seq.exists (fun symbolUse -> symbolUse.IsFromUse && symbolUse.Symbol.DisplayName = "F2")
536+
|> shouldEqual true
537+
538+
[<Test>]
539+
let ``Unresolved record field 05`` () =
540+
let _, checkResults = getParseAndCheckResults """
541+
[<RequireQualifiedAccess>]
542+
type R =
543+
{ F1: int
544+
F2: int }
545+
546+
match Unchecked.defaultof<R> with
547+
{ F = 1
548+
R.F2 = 1 } -> ()
549+
"""
550+
getSymbolUses checkResults
551+
|> Seq.exists (fun symbolUse -> symbolUse.IsFromUse && symbolUse.Symbol.DisplayName = "F2")
552+
|> shouldEqual true
553+
554+
555+
[<Test>]
556+
let ``Unresolved record field 06`` () =
557+
let _, checkResults = getParseAndCheckResults """
558+
[<RequireQualifiedAccess>]
559+
type R =
560+
{ F1: int
561+
F2: int }
562+
563+
match Unchecked.defaultof<R> with
564+
{ R.F2 = 1
565+
F = 1 } -> ()
566+
"""
567+
getSymbolUses checkResults
568+
|> Seq.exists (fun symbolUse -> symbolUse.IsFromUse && symbolUse.Symbol.DisplayName = "F2")
569+
|> shouldEqual true

0 commit comments

Comments
 (0)