diff --git a/src/fsharp/CompileOps.fs b/src/fsharp/CompileOps.fs index 069dba4859e..a2bb29d4cc7 100644 --- a/src/fsharp/CompileOps.fs +++ b/src/fsharp/CompileOps.fs @@ -153,6 +153,7 @@ let GetRangeOfDiagnostic(err:PhasedDiagnostic) = | InterfaceNotRevealed(_, _, m) | WrappedError (_, m) | PatternMatchCompilation.MatchIncomplete (_, _, m) + | PatternMatchCompilation.EnumMatchIncomplete (_, _, m) | PatternMatchCompilation.RuleNeverMatched m | ValNotMutable(_, _, m) | ValNotLocal(_, _, m) @@ -354,6 +355,7 @@ let GetDiagnosticNumber(err:PhasedDiagnostic) = | ExtensionTyping.ProvidedTypeResolutionNoRange _ | ExtensionTyping.ProvidedTypeResolution _ -> 103 #endif + | PatternMatchCompilation.EnumMatchIncomplete _ -> 104 (* DO NOT CHANGE THE NUMBERS *) // Strip TargetInvocationException wrappers @@ -559,6 +561,7 @@ let MatchIncomplete2E() = DeclareResourceString("MatchIncomplete2", "%s") let MatchIncomplete3E() = DeclareResourceString("MatchIncomplete3", "%s") let MatchIncomplete4E() = DeclareResourceString("MatchIncomplete4", "") let RuleNeverMatchedE() = DeclareResourceString("RuleNeverMatched", "") +let EnumMatchIncomplete1E() = DeclareResourceString("EnumMatchIncomplete1", "") let ValNotMutableE() = DeclareResourceString("ValNotMutable", "%s") let ValNotLocalE() = DeclareResourceString("ValNotLocal", "") let Obsolete1E() = DeclareResourceString("Obsolete1", "") @@ -1401,6 +1404,15 @@ let OutputPhasedErrorR (os:StringBuilder) (err:PhasedDiagnostic) = if isComp then os.Append(MatchIncomplete4E().Format) |> ignore + | PatternMatchCompilation.EnumMatchIncomplete (isComp, cexOpt, _) -> + os.Append(EnumMatchIncomplete1E().Format) |> ignore + match cexOpt with + | None -> () + | Some (cex, false) -> os.Append(MatchIncomplete2E().Format cex) |> ignore + | Some (cex, true) -> os.Append(MatchIncomplete3E().Format cex) |> ignore + if isComp then + os.Append(MatchIncomplete4E().Format) |> ignore + | PatternMatchCompilation.RuleNeverMatched _ -> os.Append(RuleNeverMatchedE().Format) |> ignore | ValNotMutable(_, valRef, _) -> os.Append(ValNotMutableE().Format(valRef.DisplayName)) |> ignore diff --git a/src/fsharp/FSStrings.resx b/src/fsharp/FSStrings.resx index eb8a8f975cf..bdbe391db2a 100644 --- a/src/fsharp/FSStrings.resx +++ b/src/fsharp/FSStrings.resx @@ -969,6 +969,9 @@ Unmatched elements will be ignored. + + Enums may take values outside known cases. + This rule will never be matched diff --git a/src/fsharp/PatternMatchCompilation.fs b/src/fsharp/PatternMatchCompilation.fs index a40292a3565..c2e0c6376dd 100644 --- a/src/fsharp/PatternMatchCompilation.fs +++ b/src/fsharp/PatternMatchCompilation.fs @@ -19,6 +19,7 @@ open Microsoft.FSharp.Compiler.Lib exception MatchIncomplete of bool * (string * bool) option * range exception RuleNeverMatched of range +exception EnumMatchIncomplete of bool * (string * bool) option * range type ActionOnFailure = | ThrowIncompleteMatchException @@ -177,33 +178,37 @@ let RefuteDiscrimSet g m path discrims = | PathConj (p,_j) -> go p tm | PathTuple (p,tys,j) -> - go p (fun _ -> mkRefTupled g m (mkOneKnown tm j tys) tys) + let k, eCoversVals = mkOneKnown tm j tys + go p (fun _ -> mkRefTupled g m k tys, eCoversVals) | PathRecd (p,tcref,tinst,j) -> - let flds = tcref |> actualTysOfInstanceRecdFields (mkTyconRefInst tcref tinst) |> mkOneKnown tm j - go p (fun _ -> Expr.Op(TOp.Recd(RecdExpr, tcref),tinst, flds,m)) + let flds, eCoversVals = tcref |> actualTysOfInstanceRecdFields (mkTyconRefInst tcref tinst) |> mkOneKnown tm j + go p (fun _ -> Expr.Op(TOp.Recd(RecdExpr, tcref),tinst, flds,m), eCoversVals) | PathUnionConstr (p,ucref,tinst,j) -> - let flds = ucref |> actualTysOfUnionCaseFields (mkTyconRefInst ucref.TyconRef tinst)|> mkOneKnown tm j - go p (fun _ -> Expr.Op(TOp.UnionCase(ucref),tinst, flds,m)) + let flds, eCoversVals = ucref |> actualTysOfUnionCaseFields (mkTyconRefInst ucref.TyconRef tinst)|> mkOneKnown tm j + go p (fun _ -> Expr.Op(TOp.UnionCase(ucref),tinst, flds,m), eCoversVals) | PathArray (p,ty,len,n) -> - go p (fun _ -> Expr.Op(TOp.Array,[ty], mkOneKnown tm n (List.replicate len ty) ,m)) + let flds, eCoversVals = mkOneKnown tm n (List.replicate len ty) + go p (fun _ -> Expr.Op(TOp.Array,[ty], flds ,m), eCoversVals) | PathExnConstr (p,ecref,n) -> - let flds = ecref |> recdFieldTysOfExnDefRef |> mkOneKnown tm n - go p (fun _ -> Expr.Op(TOp.ExnConstr(ecref),[], flds,m)) + let flds, eCoversVals = ecref |> recdFieldTysOfExnDefRef |> mkOneKnown tm n + go p (fun _ -> Expr.Op(TOp.ExnConstr(ecref),[], flds,m), eCoversVals) | PathEmpty(ty) -> tm ty - and mkOneKnown tm n tys = List.mapi (fun i ty -> if i = n then tm ty else mkUnknown ty) tys - and mkUnknowns tys = List.map mkUnknown tys + and mkOneKnown tm n tys = + let flds = List.mapi (fun i ty -> if i = n then tm ty else (mkUnknown ty, false)) tys + List.map fst flds, List.fold (fun acc (_, eCoversVals) -> eCoversVals || acc) false flds + and mkUnknowns tys = List.map (fun x -> mkUnknown x) tys let tm ty = match discrims with | [DecisionTreeTest.IsNull] -> - snd(mkCompGenLocal m notNullText ty) + snd(mkCompGenLocal m notNullText ty), false | [DecisionTreeTest.IsInst (_,_)] -> - snd(mkCompGenLocal m otherSubtypeText ty) + snd(mkCompGenLocal m otherSubtypeText ty), false | (DecisionTreeTest.Const c :: rest) -> let consts = Set.ofList (c :: List.choose (function DecisionTreeTest.Const(c) -> Some c | _ -> None) rest) let c' = @@ -227,12 +232,23 @@ let RefuteDiscrimSet g m path discrims = | Const.Decimal _ -> seq { 1 .. System.Int32.MaxValue } |> Seq.map (fun v -> Const.Decimal(decimal v)) | _ -> raise CannotRefute) + + let coversKnownEnumValues = + match tryDestAppTy g ty with + | Some tcref when tcref.IsEnumTycon -> + let knownValues = + tcref.AllFieldsArray |> Array.choose (fun f -> + match f.rfield_const, f.rfield_static with + | Some value, true -> Some value + | _, _ -> None) + Array.forall (fun ev -> consts.Contains ev) knownValues + | _ -> false (* REVIEW: we could return a better enumeration literal field here if a field matches one of the enumeration cases *) match c' with | None -> raise CannotRefute - | Some c -> Expr.Const(c,m,ty) + | Some c -> Expr.Const(c,m,ty), coversKnownEnumValues | (DecisionTreeTest.UnionCase (ucref1,tinst) :: rest) -> let ucrefs = ucref1 :: List.choose (function DecisionTreeTest.UnionCase(ucref,_) -> Some ucref | _ -> None) rest @@ -246,10 +262,10 @@ let RefuteDiscrimSet g m path discrims = | [] -> raise CannotRefute | ucref2 :: _ -> let flds = ucref2 |> actualTysOfUnionCaseFields (mkTyconRefInst tcref tinst) |> mkUnknowns - Expr.Op(TOp.UnionCase(ucref2),tinst, flds,m) + Expr.Op(TOp.UnionCase(ucref2),tinst, flds,m), false | [DecisionTreeTest.ArrayLength (n,ty)] -> - Expr.Op(TOp.Array,[ty], mkUnknowns (List.replicate (n+1) ty) ,m) + Expr.Op(TOp.Array,[ty], mkUnknowns (List.replicate (n+1) ty) ,m), false | _ -> raise CannotRefute @@ -302,15 +318,16 @@ let rec CombineRefutations g r1 r2 = let ShowCounterExample g denv m refuted = try let refutations = refuted |> List.collect (function RefutedWhenClause -> [] | (RefutedInvestigation(path,discrim)) -> [RefuteDiscrimSet g m path discrim]) - let counterExample = + let counterExample, enumCoversKnown = match refutations with | [] -> raise CannotRefute - | h :: t -> - if verbose then dprintf "h = %s\n" (Layout.showL (exprL h)) - List.fold (CombineRefutations g) h t + | (r, eck) :: t -> + if verbose then dprintf "r = %s (enumCoversKnownValue = %b)\n" (Layout.showL (exprL r)) eck + List.fold (fun (rAcc, eckAcc) (r, eck) -> + CombineRefutations g rAcc r, eckAcc || eck) (r, eck) t let text = Layout.showL (NicePrint.dataExprL denv counterExample) let failingWhenClause = refuted |> List.exists (function RefutedWhenClause -> true | _ -> false) - Some(text,failingWhenClause) + Some(text,failingWhenClause,enumCoversKnown) with | CannotRefute -> @@ -689,10 +706,15 @@ let CompilePatternBasic (* Emit the incomplete match warning *) if warnOnIncomplete then match actionOnFailure with - | ThrowIncompleteMatchException -> - warning (MatchIncomplete (false,ShowCounterExample g denv matchm refuted, matchm)) - | IgnoreWithWarning -> - warning (MatchIncomplete (true,ShowCounterExample g denv matchm refuted, matchm)) + | ThrowIncompleteMatchException | IgnoreWithWarning -> + let ignoreWithWarning = (actionOnFailure = IgnoreWithWarning) + match ShowCounterExample g denv matchm refuted with + | Some(text,failingWhenClause,true) -> + warning (EnumMatchIncomplete(ignoreWithWarning, Some(text,failingWhenClause), matchm)) + | Some(text,failingWhenClause,false) -> + warning (MatchIncomplete(ignoreWithWarning, Some(text,failingWhenClause), matchm)) + | None -> + warning (MatchIncomplete(ignoreWithWarning, None, matchm)) | _ -> () diff --git a/src/fsharp/PatternMatchCompilation.fsi b/src/fsharp/PatternMatchCompilation.fsi index f2cbce1e993..160396caf04 100644 --- a/src/fsharp/PatternMatchCompilation.fsi +++ b/src/fsharp/PatternMatchCompilation.fsi @@ -67,3 +67,4 @@ val internal CompilePattern : exception internal MatchIncomplete of bool * (string * bool) option * range exception internal RuleNeverMatched of range +exception internal EnumMatchIncomplete of bool * (string * bool) option * range \ No newline at end of file diff --git a/src/fsharp/xlf/FSStrings.cs.xlf b/src/fsharp/xlf/FSStrings.cs.xlf index 90e272e863b..67c7b734576 100644 --- a/src/fsharp/xlf/FSStrings.cs.xlf +++ b/src/fsharp/xlf/FSStrings.cs.xlf @@ -1422,6 +1422,11 @@ Nespárované prvky se budou ignorovat. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Pro toto pravidlo nebude nikdy existovat shoda. diff --git a/src/fsharp/xlf/FSStrings.de.xlf b/src/fsharp/xlf/FSStrings.de.xlf index 1c98a1cdb01..e29b2908ab5 100644 --- a/src/fsharp/xlf/FSStrings.de.xlf +++ b/src/fsharp/xlf/FSStrings.de.xlf @@ -1422,6 +1422,11 @@ Nicht zugeordnete Elemente werden ignoriert. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Für diese Regel wird niemals eine Übereinstimmung gefunden. diff --git a/src/fsharp/xlf/FSStrings.en.xlf b/src/fsharp/xlf/FSStrings.en.xlf index 03bc0887740..af9ffda10a3 100644 --- a/src/fsharp/xlf/FSStrings.en.xlf +++ b/src/fsharp/xlf/FSStrings.en.xlf @@ -1422,6 +1422,11 @@ Unmatched elements will be ignored. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched This rule will never be matched diff --git a/src/fsharp/xlf/FSStrings.es.xlf b/src/fsharp/xlf/FSStrings.es.xlf index 986deafbc5c..806ff3fdc43 100644 --- a/src/fsharp/xlf/FSStrings.es.xlf +++ b/src/fsharp/xlf/FSStrings.es.xlf @@ -1422,6 +1422,11 @@ Los elementos que no coinciden se omitirán. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Nunca se buscarán coincidencias con esta regla. diff --git a/src/fsharp/xlf/FSStrings.fr.xlf b/src/fsharp/xlf/FSStrings.fr.xlf index 4d4cccb39fe..5697902a66d 100644 --- a/src/fsharp/xlf/FSStrings.fr.xlf +++ b/src/fsharp/xlf/FSStrings.fr.xlf @@ -1422,6 +1422,11 @@ Les éléments non appariés seront ignorés. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Cette règle n'aura aucune correspondance diff --git a/src/fsharp/xlf/FSStrings.it.xlf b/src/fsharp/xlf/FSStrings.it.xlf index e26f38b3270..f3413f1ec3c 100644 --- a/src/fsharp/xlf/FSStrings.it.xlf +++ b/src/fsharp/xlf/FSStrings.it.xlf @@ -1422,6 +1422,11 @@ Gli elementi senza corrispondenza verranno ignorati. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Questa regola non avrà mai alcuna corrispondenza diff --git a/src/fsharp/xlf/FSStrings.ja.xlf b/src/fsharp/xlf/FSStrings.ja.xlf index 7256d1b1a49..a56e6c210fd 100644 --- a/src/fsharp/xlf/FSStrings.ja.xlf +++ b/src/fsharp/xlf/FSStrings.ja.xlf @@ -1422,6 +1422,11 @@ 一致しない要素は無視されます。 + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched この規則には一致しません diff --git a/src/fsharp/xlf/FSStrings.ko.xlf b/src/fsharp/xlf/FSStrings.ko.xlf index 2ba29ac4307..2d5c7a3e1c1 100644 --- a/src/fsharp/xlf/FSStrings.ko.xlf +++ b/src/fsharp/xlf/FSStrings.ko.xlf @@ -1422,6 +1422,11 @@ 일치하지 않는 요소는 무시됩니다. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched 이 규칙은 일치시킬 수 없습니다. diff --git a/src/fsharp/xlf/FSStrings.pl.xlf b/src/fsharp/xlf/FSStrings.pl.xlf index d544bdcc32e..3ad66dba418 100644 --- a/src/fsharp/xlf/FSStrings.pl.xlf +++ b/src/fsharp/xlf/FSStrings.pl.xlf @@ -1422,6 +1422,11 @@ Niedopasowane elementy zostaną zignorowane. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Ta reguła nigdy nie zostanie dopasowana diff --git a/src/fsharp/xlf/FSStrings.pt-BR.xlf b/src/fsharp/xlf/FSStrings.pt-BR.xlf index b1dccf3db10..7a747c6f86b 100644 --- a/src/fsharp/xlf/FSStrings.pt-BR.xlf +++ b/src/fsharp/xlf/FSStrings.pt-BR.xlf @@ -1422,6 +1422,11 @@ Elementos incompatíveis serão ignorados. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Esta regra nunca será correspondida diff --git a/src/fsharp/xlf/FSStrings.ru.xlf b/src/fsharp/xlf/FSStrings.ru.xlf index bd45c8d8eee..9c8a69c516b 100644 --- a/src/fsharp/xlf/FSStrings.ru.xlf +++ b/src/fsharp/xlf/FSStrings.ru.xlf @@ -1422,6 +1422,11 @@ Элементы без соответствий будут проигнорированы. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Данное правило никогда не будет сопоставлено diff --git a/src/fsharp/xlf/FSStrings.tr.xlf b/src/fsharp/xlf/FSStrings.tr.xlf index 6ce85a3cc4c..3e13a11f720 100644 --- a/src/fsharp/xlf/FSStrings.tr.xlf +++ b/src/fsharp/xlf/FSStrings.tr.xlf @@ -1422,6 +1422,11 @@ Eşleşmeyen öğeler yok sayılacak. + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched Bu kural hiçbir zaman eşleştirilmeyecek diff --git a/src/fsharp/xlf/FSStrings.zh-Hans.xlf b/src/fsharp/xlf/FSStrings.zh-Hans.xlf index 235affe46bb..2101fdbe005 100644 --- a/src/fsharp/xlf/FSStrings.zh-Hans.xlf +++ b/src/fsharp/xlf/FSStrings.zh-Hans.xlf @@ -1422,6 +1422,11 @@ 将忽略不匹配的元素。 + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched 从不与此规则匹配 diff --git a/src/fsharp/xlf/FSStrings.zh-Hant.xlf b/src/fsharp/xlf/FSStrings.zh-Hant.xlf index 90c5a5a8066..05a07e93b9d 100644 --- a/src/fsharp/xlf/FSStrings.zh-Hant.xlf +++ b/src/fsharp/xlf/FSStrings.zh-Hant.xlf @@ -1422,6 +1422,11 @@ 無對應的項目將會被忽略。 + + Enums may take values outside known cases. + Enums may take values outside known cases. + + This rule will never be matched 這個規則絕不會比對到 diff --git a/tests/fsharp/tests.fs b/tests/fsharp/tests.fs index 3c2d9c2d792..cb12953d7ed 100644 --- a/tests/fsharp/tests.fs +++ b/tests/fsharp/tests.fs @@ -2162,6 +2162,9 @@ module TypecheckTests = [] let ``type check neg101`` () = singleNegTest (testConfig "typecheck/sigs") "neg101" + [] + let ``type check neg102`` () = singleNegTest (testConfig "typecheck/sigs") "neg102" + [] let ``type check neg_byref_1`` () = singleNegTest (testConfig "typecheck/sigs") "neg_byref_1" diff --git a/tests/fsharp/typecheck/sigs/neg102.bsl b/tests/fsharp/typecheck/sigs/neg102.bsl new file mode 100644 index 00000000000..2ef6680cd02 --- /dev/null +++ b/tests/fsharp/typecheck/sigs/neg102.bsl @@ -0,0 +1,10 @@ + +neg102.fs(8,14,8,22): typecheck error FS0025: Incomplete pattern matches on this expression. For example, the value 'enum (1)' may indicate a case not covered by the pattern(s). + +neg102.fs(11,14,11,22): typecheck error FS0025: Incomplete pattern matches on this expression. For example, the value 'B' may indicate a case not covered by the pattern(s). + +neg102.fs(14,14,14,22): typecheck error FS0025: Incomplete pattern matches on this expression. For example, the value '0' may indicate a case not covered by the pattern(s). + +neg102.fs(20,14,20,22): typecheck error FS0104: Enums may take values outside known cases. For example, the value 'enum (2)' may indicate a case not covered by the pattern(s). + +neg102.fs(24,14,24,22): typecheck error FS0104: Enums may take values outside known cases. For example, the value 'Some (enum (2))' may indicate a case not covered by the pattern(s). diff --git a/tests/fsharp/typecheck/sigs/neg102.fs b/tests/fsharp/typecheck/sigs/neg102.fs new file mode 100644 index 00000000000..7efb9171763 --- /dev/null +++ b/tests/fsharp/typecheck/sigs/neg102.fs @@ -0,0 +1,27 @@ +module M +type EnumAB = A = 0 | B = 1 +type UnionAB = A | B + +module FS0025 = + // All of these should emit warning FS0025 ("Incomplete pattern match....") + + let f1 = function + | EnumAB.A -> "A" + + let f2 = function + | UnionAB.A -> "A" + + let f3 = function + | 42 -> "forty-two" + +module FS0104 = + // These should emit warning FS0104 ("Enums may take values outside of known cases....") + + let f1 = function + | EnumAB.A -> "A" + | EnumAB.B -> "B" + + let f2 = function + | Some(EnumAB.A) -> "A" + | Some(EnumAB.B) -> "B" + | None -> "none" \ No newline at end of file diff --git a/tests/fsharpqa/Source/Conformance/PatternMatching/Expression/W_CounterExampleWithEnum02.fs b/tests/fsharpqa/Source/Conformance/PatternMatching/Expression/W_CounterExampleWithEnum02.fs index 0fcb3e090de..cb0e64ccf83 100644 --- a/tests/fsharpqa/Source/Conformance/PatternMatching/Expression/W_CounterExampleWithEnum02.fs +++ b/tests/fsharpqa/Source/Conformance/PatternMatching/Expression/W_CounterExampleWithEnum02.fs @@ -1,6 +1,6 @@ // #Regression #Conformance #PatternMatching // Regression test for DevDiv:198999 ("Warning messages for incomplete matches involving enum types are wrong") -//Incomplete pattern matches on this expression\. For example, the value 'enum \(2\)' may indicate a case not covered by the pattern\(s\)\.$ +//Enums may take values outside known cases\. For example, the value 'enum \(2\)' may indicate a case not covered by the pattern\(s\)\.$ //Incomplete pattern matches on this expression\. For example, the value 'enum \(1\)' may indicate a case not covered by the pattern\(s\)\.$ //Incomplete pattern matches on this expression\. For example, the value 'enum \(1\)' may indicate a case not covered by the pattern\(s\)\.$ //Incomplete pattern matches on this expression\. For example, the value 'enum \(1\)' may indicate a case not covered by the pattern\(s\)\.$ diff --git a/tests/fsharpqa/Source/Conformance/PatternMatching/Simple/E_namedLiberal01.fs b/tests/fsharpqa/Source/Conformance/PatternMatching/Simple/E_namedLiberal01.fs index d306ece633b..2f3ba740856 100644 --- a/tests/fsharpqa/Source/Conformance/PatternMatching/Simple/E_namedLiberal01.fs +++ b/tests/fsharpqa/Source/Conformance/PatternMatching/Simple/E_namedLiberal01.fs @@ -1,8 +1,7 @@ // #Regression #Conformance #PatternMatching -// Match warning when using enum for incomplete match. -// (Even if you use every possible value. +// Match warning when covering all defined values of an enum -//Incomplete pattern matches on this expression +//Enums may take values outside known cases. open System