diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs index 027beea0b0eecbba2a55dbf0dca12e45a861bedf..2daf42fd25152fb60599f6889f1aa9248c4cd9a7 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs @@ -1415,11 +1415,6 @@ private void ExactInference(TypeSymbolWithAnnotations source, TypeSymbolWithAnno return; } - if (ExactTupleInference(source, target, ref useSiteDiagnostics)) - { - return; - } - // SPEC: * Otherwise, if V is a constructed type C and U is a constructed // SPEC: type C then an exact inference is made // SPEC: from each Ui to the corresponding Vi. @@ -1527,7 +1522,7 @@ private bool ExactNullableInference(TypeSymbolWithAnnotations source, TypeSymbol return ExactOrBoundsNullableInference(ExactOrBoundsKind.Exact, source, target, ref useSiteDiagnostics); } - private bool ExactOrBoundsTupleInference(ExactOrBoundsKind kind, TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations target, ref HashSet useSiteDiagnostics) + private bool LowerBoundTupleInference(TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations target, ref HashSet useSiteDiagnostics) { Debug.Assert(!source.IsNull); Debug.Assert(!target.IsNull); @@ -1548,17 +1543,12 @@ private bool ExactOrBoundsTupleInference(ExactOrBoundsKind kind, TypeSymbolWithA for (int i = 0; i < sourceTypes.Length; i++) { - ExactOrBoundsInference(kind, sourceTypes[i], targetTypes[i], ref useSiteDiagnostics); + LowerBoundInference(sourceTypes[i], targetTypes[i], ref useSiteDiagnostics); } return true; } - private bool ExactTupleInference(TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations target, ref HashSet useSiteDiagnostics) - { - return ExactOrBoundsTupleInference(ExactOrBoundsKind.Exact, source, target, ref useSiteDiagnostics); - } - private bool ExactConstructedInference(TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations target, ref HashSet useSiteDiagnostics) { Debug.Assert(!source.IsNull); @@ -1802,11 +1792,6 @@ private bool LowerBoundNullableInference(TypeSymbolWithAnnotations source, TypeS return ExactOrBoundsNullableInference(ExactOrBoundsKind.LowerBound, source, target, ref useSiteDiagnostics); } - private bool LowerBoundTupleInference(TypeSymbolWithAnnotations source, TypeSymbolWithAnnotations target, ref HashSet useSiteDiagnostics) - { - return ExactOrBoundsTupleInference(ExactOrBoundsKind.LowerBound, source, target, ref useSiteDiagnostics); - } - private bool LowerBoundConstructedInference(TypeSymbol source, TypeSymbol target, ref HashSet useSiteDiagnostics) { Debug.Assert((object)source != null); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index c8f03316ac349f76887080b9726e5a1c18ba46da..34f134a75e556c1ca7b2016ca88e76f73d8e2e4e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4648,22 +4648,7 @@ protected override void UnionWith(ref LocalState self, ref LocalState other) { NullableAnnotation selfAnnotation = self[slot]; NullableAnnotation otherAnnotation = other[slot]; - NullableAnnotation union; - - if (selfAnnotation.IsAnyNotNullable() || otherAnnotation.IsAnyNotNullable()) - { - union = selfAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis || otherAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis ? - NullableAnnotation.NotNullableBasedOnAnalysis : NullableAnnotation.NotNullable; - } - else if (selfAnnotation == NullableAnnotation.Unknown || otherAnnotation == NullableAnnotation.Unknown) - { - union = NullableAnnotation.Unknown; - } - else - { - union = selfAnnotation == NullableAnnotation.NullableBasedOnAnalysis || otherAnnotation == NullableAnnotation.NullableBasedOnAnalysis ? - NullableAnnotation.NullableBasedOnAnalysis : NullableAnnotation.Nullable; - } + NullableAnnotation union = selfAnnotation.MeetForFlowAnalysisFinally(otherAnnotation); if (selfAnnotation != union) { @@ -4688,57 +4673,7 @@ protected override bool IntersectWith(ref LocalState self, ref LocalState other) { NullableAnnotation selfAnnotation = self[slot]; NullableAnnotation otherAnnotation = other[slot]; - NullableAnnotation intersection; - - if (selfAnnotation.IsAnyNullable() || otherAnnotation.IsAnyNullable()) - { - intersection = selfAnnotation == NullableAnnotation.Nullable || otherAnnotation == NullableAnnotation.Nullable ? - NullableAnnotation.Nullable : NullableAnnotation.NullableBasedOnAnalysis; - } - else if (selfAnnotation == NullableAnnotation.Unknown) - { - if (otherAnnotation == NullableAnnotation.Unknown || otherAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis) - { - intersection = NullableAnnotation.Unknown; - } - else - { - Debug.Assert(otherAnnotation == NullableAnnotation.NotNullable); - if (isPossiblyNullableReferenceTypeTypeParameter(slot)) - { - intersection = otherAnnotation; - } - else - { - intersection = NullableAnnotation.Unknown; - } - } - } - else if (otherAnnotation == NullableAnnotation.Unknown) - { - if (selfAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis) - { - intersection = NullableAnnotation.Unknown; - } - else - { - Debug.Assert(selfAnnotation == NullableAnnotation.NotNullable); - if (isPossiblyNullableReferenceTypeTypeParameter(slot)) - { - intersection = selfAnnotation; - } - else - { - intersection = NullableAnnotation.Unknown; - } - } - } - else - { - intersection = selfAnnotation == NullableAnnotation.NotNullable || otherAnnotation == NullableAnnotation.NotNullable ? - NullableAnnotation.NotNullable : NullableAnnotation.NotNullableBasedOnAnalysis; - } - + NullableAnnotation intersection = selfAnnotation.JoinForFlowAnalysisBranches(otherAnnotation, (slot, this), isPossiblyNullableReferenceTypeTypeParameterDelegate); if (selfAnnotation != intersection) { self[slot] = intersection; @@ -4758,14 +4693,14 @@ protected override bool IntersectWith(ref LocalState self, ref LocalState other) Debug.Assert(!other.Reachable); return false; } - - bool isPossiblyNullableReferenceTypeTypeParameter(int slot) - { - Symbol symbol = variableBySlot[slot].Symbol; - return (object)symbol != null && VariableType(symbol).TypeSymbol?.IsPossiblyNullableReferenceTypeTypeParameter() == true; - } } + private readonly static Func<(int slot, NullableWalker self), bool> isPossiblyNullableReferenceTypeTypeParameterDelegate = args => + { + Symbol symbol = args.self.variableBySlot[args.slot].Symbol; + return (object)symbol != null && VariableType(symbol).TypeSymbol?.IsPossiblyNullableReferenceTypeTypeParameter() == true; + }; + [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] #if REFERENCE_STATE internal class LocalState : AbstractLocalState diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolWithAnnotations.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolWithAnnotations.cs index 4e6c4276d8dc25e3db57b1d253c83f9e15240979..449d500c35997bd759ce6786995114d0f42154bf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolWithAnnotations.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolWithAnnotations.cs @@ -31,6 +31,222 @@ public static bool IsAnyNotNullable(this NullableAnnotation annotation) { return annotation == NullableAnnotation.NotNullable || annotation == NullableAnnotation.NotNullableBasedOnAnalysis; } + + /// + /// Join nullable annotations from the set of lower bounds for fixing a type parameter. + /// + public static NullableAnnotation JoinForFixingLowerBounds(this NullableAnnotation a, NullableAnnotation b) + { + if (a == b) + { + return a; + } + + if (a.IsAnyNullable() && b.IsAnyNullable()) + { + return NullableAnnotation.Nullable; + } + + // If nullability on both sides matches - result is that nullability (trivial cases like these are handled before the switch) + // If either candidate is nullable - result is nullable + // Otherwise - result is "oblivious". + + if (a.IsAnyNullable()) + { + Debug.Assert(!b.IsAnyNullable()); + return a; + } + + if (b.IsAnyNullable()) + { + return b; + } + + if (a == NullableAnnotation.Unknown || b == NullableAnnotation.Unknown) + { + return NullableAnnotation.Unknown; + } + + Debug.Assert((a == NullableAnnotation.NotNullable && b == NullableAnnotation.NotNullableBasedOnAnalysis) || + (b == NullableAnnotation.NotNullable && a == NullableAnnotation.NotNullableBasedOnAnalysis)); + return NullableAnnotation.NotNullable; // It is reasonable to settle on this value because the difference in annotations is either + // not significant for the type, or candidate corresponding to this value is possibly a + // nullable reference type type parameter and nullable should win. + } + + /// + /// Join nullable annotations from distinct branches during flow analysis. + /// + public static NullableAnnotation JoinForFlowAnalysisBranches(this NullableAnnotation selfAnnotation, NullableAnnotation otherAnnotation, T type, Func isPossiblyNullableReferenceTypeTypeParameter) + { + if (selfAnnotation == otherAnnotation) + { + return selfAnnotation; + } + + if (selfAnnotation.IsAnyNullable() || otherAnnotation.IsAnyNullable()) + { + return selfAnnotation == NullableAnnotation.Nullable || otherAnnotation == NullableAnnotation.Nullable ? + NullableAnnotation.Nullable : NullableAnnotation.NullableBasedOnAnalysis; + } + else if (selfAnnotation == NullableAnnotation.Unknown) + { + if (otherAnnotation == NullableAnnotation.Unknown || otherAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis) + { + return NullableAnnotation.Unknown; + } + else + { + Debug.Assert(otherAnnotation == NullableAnnotation.NotNullable); + if (isPossiblyNullableReferenceTypeTypeParameter(type)) + { + return otherAnnotation; + } + else + { + return NullableAnnotation.Unknown; + } + } + } + else if (otherAnnotation == NullableAnnotation.Unknown) + { + if (selfAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis) + { + return NullableAnnotation.Unknown; + } + else + { + Debug.Assert(selfAnnotation == NullableAnnotation.NotNullable); + if (isPossiblyNullableReferenceTypeTypeParameter(type)) + { + return selfAnnotation; + } + else + { + return NullableAnnotation.Unknown; + } + } + } + else + { + return selfAnnotation == NullableAnnotation.NotNullable || otherAnnotation == NullableAnnotation.NotNullable ? + NullableAnnotation.NotNullable : NullableAnnotation.NotNullableBasedOnAnalysis; + } + } + + /// + /// Meet two nullable annotations for computing the nullable annotation of a type parameter from upper bounds. + /// + public static NullableAnnotation MeetForFixingUpperBounds(this NullableAnnotation a, NullableAnnotation b) + { + if (a == b) + { + return a; + } + + if (a.IsAnyNullable() && b.IsAnyNullable()) + { + return NullableAnnotation.Nullable; + } + + // If nullability on both sides matches - result is that nullability (trivial cases like these are handled before the switch) + // If either candidate is not nullable - result is not nullable + // Otherwise - result is "oblivious". + + if (a == NullableAnnotation.NotNullableBasedOnAnalysis || b == NullableAnnotation.NotNullableBasedOnAnalysis) + { + return NullableAnnotation.NotNullableBasedOnAnalysis; + } + + if (a == NullableAnnotation.NotNullable || b == NullableAnnotation.NotNullable) + { + return NullableAnnotation.NotNullable; + } + + Debug.Assert(a == NullableAnnotation.Unknown || b == NullableAnnotation.Unknown); + return NullableAnnotation.Unknown; + } + + /// + /// Meet two nullable annotations from distinct states for the meet (union) operation in flow analysis. + /// + public static NullableAnnotation MeetForFlowAnalysisFinally(this NullableAnnotation selfAnnotation, NullableAnnotation otherAnnotation) + { + if (selfAnnotation == otherAnnotation) + { + return selfAnnotation; + } + + if (selfAnnotation.IsAnyNotNullable() || otherAnnotation.IsAnyNotNullable()) + { + return selfAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis || otherAnnotation == NullableAnnotation.NotNullableBasedOnAnalysis ? + NullableAnnotation.NotNullableBasedOnAnalysis : NullableAnnotation.NotNullable; + } + else if (selfAnnotation == NullableAnnotation.Unknown || otherAnnotation == NullableAnnotation.Unknown) + { + return NullableAnnotation.Unknown; + } + else + { + return selfAnnotation == NullableAnnotation.NullableBasedOnAnalysis || otherAnnotation == NullableAnnotation.NullableBasedOnAnalysis ? + NullableAnnotation.NullableBasedOnAnalysis : NullableAnnotation.Nullable; + } + } + + /// + /// Check that two nullable annotations are "compatible", which means they could be the same. Return the + /// nullable annotation to be used as a result. Also returns through + /// whether the caller should report a warning because there was an actual mismatch (e.g. nullable vs non-nullable). + /// + public static NullableAnnotation EnsureCompatible(this NullableAnnotation a, NullableAnnotation b, T type, Func isPossiblyNullableReferenceTypeTypeParameter, out bool hadNullabilityMismatch) + { + hadNullabilityMismatch = false; + if (a == b) + { + return a; + } + + if (a.IsAnyNullable() && b.IsAnyNullable()) + { + return NullableAnnotation.Nullable; + } + + // If nullability on both sides matches - result is that nullability (trivial cases like these are handled before the switch) + // If either candidate is "oblivious" - result is the nullability of the other candidate + // Otherwise - we declare a mismatch and result is not nullable. + + if (a == NullableAnnotation.Unknown) + { + return b; + } + + if (b == NullableAnnotation.Unknown) + { + return a; + } + + // At this point we know that either nullability of both sides is significantly different NotNullable vs. Nullable, + // or we are dealing with different flavors of not nullable for both candidates + if ((a == NullableAnnotation.NotNullable && b == NullableAnnotation.NotNullableBasedOnAnalysis) || + (b == NullableAnnotation.NotNullable && a == NullableAnnotation.NotNullableBasedOnAnalysis)) + { + if (!isPossiblyNullableReferenceTypeTypeParameter(type)) + { + // For this type both not nullable annotations are equivalent and therefore match. + return NullableAnnotation.NotNullable; + } + + // We are dealing with different flavors of not nullable for a possibly nullable reference type parameter, + // we don't have a reliable way to merge them since one of them can actually represent a nullable type. + } + else + { + Debug.Assert(a.IsAnyNullable() != b.IsAnyNullable()); + } + + hadNullabilityMismatch = true; + return NullableAnnotation.NotNullable; + } } /// @@ -309,104 +525,21 @@ internal TypeSymbolWithAnnotations MergeNullability(TypeSymbolWithAnnotations ot private static NullableAnnotation MergeNullableAnnotation(TypeSymbol type, NullableAnnotation a, NullableAnnotation b, VarianceKind variance, out bool hadNullabilityMismatch) { hadNullabilityMismatch = false; - if (a == b) - { - return a; - } - - if (a.IsAnyNullable() && b.IsAnyNullable()) - { - return NullableAnnotation.Nullable; - } - switch (variance) { case VarianceKind.In: - - // If nullability on both sides matches - result is that nullability (trivial cases like these are handled before the switch) - // If either candidate is not nullable - result is not nullable - // Otherwise - result is "oblivious". - - if (a == NullableAnnotation.NotNullableBasedOnAnalysis || b == NullableAnnotation.NotNullableBasedOnAnalysis) - { - return NullableAnnotation.NotNullableBasedOnAnalysis; - } - - if (a == NullableAnnotation.NotNullable || b == NullableAnnotation.NotNullable) - { - return NullableAnnotation.NotNullable; - } - - Debug.Assert(a == NullableAnnotation.Unknown || b == NullableAnnotation.Unknown); - return NullableAnnotation.Unknown; - + return a.MeetForFixingUpperBounds(b); case VarianceKind.Out: - - // If nullability on both sides matches - result is that nullability (trivial cases like these are handled before the switch) - // If either candidate is nullable - result is nullable - // Otherwise - result is "oblivious". - - if (a.IsAnyNullable()) - { - Debug.Assert(!b.IsAnyNullable()); - return a; - } - - if (b.IsAnyNullable()) - { - return b; - } - - if (a == NullableAnnotation.Unknown || b == NullableAnnotation.Unknown) - { - return NullableAnnotation.Unknown; - } - - Debug.Assert((a == NullableAnnotation.NotNullable && b == NullableAnnotation.NotNullableBasedOnAnalysis) || - (b == NullableAnnotation.NotNullable && a == NullableAnnotation.NotNullableBasedOnAnalysis)); - return NullableAnnotation.NotNullable; // It is reasonable to settle on this value because the difference in annotations is either - // not significant for the type, or candidate corresponding to this value is possibly a - // nullable reference type type parameter and nullable should win. - + return a.JoinForFixingLowerBounds(b); + case VarianceKind.None: + return a.EnsureCompatible(b, type, _IsPossiblyNullableReferenceTypeTypeParameterDelegate, out hadNullabilityMismatch); default: - - // If nullability on both sides matches - result is that nullability (trivial cases like these are handled before the switch) - // If either candidate is "oblivious" - result is the nullability of the other candidate - // Otherwise - we declare a mismatch and result is not nullable. - - if (a == NullableAnnotation.Unknown) - { - return b; - } - if (b == NullableAnnotation.Unknown) - { - return a; - } - - // At this point we know that either nullability of both sides is significantly different NotNullable vs. Nullable, - // or we are dealing with different flavors of not nullable for both candidates - if ((a == NullableAnnotation.NotNullable && b == NullableAnnotation.NotNullableBasedOnAnalysis) || - (b == NullableAnnotation.NotNullable && a == NullableAnnotation.NotNullableBasedOnAnalysis)) - { - if (!type.IsPossiblyNullableReferenceTypeTypeParameter()) - { - // For this type both not nullable annotations are equivalent and therefore match. - return NullableAnnotation.NotNullable; - } - - // We are dealing with different flavors of not nullable for a possibly nullable reference type parameter, - // we don't have a reliable way to merge them since one of them can actually represent a nullable type. - } - else - { - Debug.Assert(a.IsAnyNullable() != b.IsAnyNullable()); - } - - hadNullabilityMismatch = true; - return NullableAnnotation.NotNullable; + throw ExceptionUtilities.UnexpectedValue(variance); } } + private readonly static Func _IsPossiblyNullableReferenceTypeTypeParameterDelegate = type => type.IsPossiblyNullableReferenceTypeTypeParameter(); + public TypeSymbolWithAnnotations WithModifiers(ImmutableArray customModifiers) => _extensions.WithModifiers(this, customModifiers); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 73b27736ed5dad77f685f368e1eb55f444a91ac7..cfe771cbf2d630ccddc607c0a68abb2ea88fed28 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -57132,5 +57132,215 @@ static void F(object? x) // y = (object)x; Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "(object)x").WithLocation(8, 13)); } + + private readonly static NullableAnnotation[] s_AllNullableAnnotations = (NullableAnnotation[])Enum.GetValues(typeof(NullableAnnotation)); + + [Fact] + public void TestJoinForFixingLowerBoundsIsAssociative() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (var c in s_AllNullableAnnotations) + { + var leftFirst = a.JoinForFixingLowerBounds(b).JoinForFixingLowerBounds(c); + var rightFirst = a.JoinForFixingLowerBounds(b.JoinForFixingLowerBounds(c)); + Assert.Equal(leftFirst, rightFirst); + } + } + } + } + + [Fact] + public void TestJoinForFlowAnalysisBranchesIsAssociative() + { + Func identity = x => x; + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (var c in s_AllNullableAnnotations) + { + foreach (bool isPossiblyNullableReferenceTypeTypeParameter in new[] { true, false }) + { + var leftFirst = a.JoinForFlowAnalysisBranches(b, isPossiblyNullableReferenceTypeTypeParameter, identity).JoinForFlowAnalysisBranches(c, isPossiblyNullableReferenceTypeTypeParameter, identity); + var rightFirst = a.JoinForFlowAnalysisBranches(b.JoinForFlowAnalysisBranches(c, isPossiblyNullableReferenceTypeTypeParameter, identity), isPossiblyNullableReferenceTypeTypeParameter, identity); + Assert.Equal(leftFirst, rightFirst); + } + } + } + } + } + + [Fact] + public void TestMeetForFixingUpperBoundsIsAssociative() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (var c in s_AllNullableAnnotations) + { + var leftFirst = a.MeetForFixingUpperBounds(b).MeetForFixingUpperBounds(c); + var rightFirst = a.MeetForFixingUpperBounds(b.MeetForFixingUpperBounds(c)); + Assert.Equal(leftFirst, rightFirst); + } + } + } + } + + [Fact] + public void TestMeetForFlowAnalysisFinallyIsAssociative() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (var c in s_AllNullableAnnotations) + { + var leftFirst = a.MeetForFlowAnalysisFinally(b).MeetForFlowAnalysisFinally(c); + var rightFirst = a.MeetForFlowAnalysisFinally(b.MeetForFlowAnalysisFinally(c)); + Assert.Equal(leftFirst, rightFirst); + } + } + } + } + + [Fact] + public void TestEnsureCompatibleIsAssociative() + { + Func identity = x => x; + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (var c in s_AllNullableAnnotations) + { + foreach (bool isPossiblyNullableReferenceTypeTypeParameter in new[] { true, false }) + { + var leftFirst = a.EnsureCompatible(b, isPossiblyNullableReferenceTypeTypeParameter, identity, out var w1a).EnsureCompatible(c, isPossiblyNullableReferenceTypeTypeParameter, identity, out var w1b); + var rightFirst = a.EnsureCompatible(b.EnsureCompatible(c, isPossiblyNullableReferenceTypeTypeParameter, identity, out var w2a), isPossiblyNullableReferenceTypeTypeParameter, identity, out var w2b); + Assert.Equal(leftFirst, rightFirst); + Assert.Equal(w1a | w1b, w2a | w2b); + } + } + } + } + } + + [Fact] + public void TestJoinForFixingLowerBoundsIsCommutative() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + var leftFirst = a.JoinForFixingLowerBounds(b); + var rightFirst = b.JoinForFixingLowerBounds(a); + Assert.Equal(leftFirst, rightFirst); + } + } + } + + [Fact] + public void TestJoinForFlowAnalysisBranchesIsCommutative() + { + Func identity = x => x; + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (bool isPossiblyNullableReferenceTypeTypeParameter in new[] { true, false }) + { + var leftFirst = a.JoinForFlowAnalysisBranches(b, isPossiblyNullableReferenceTypeTypeParameter, identity); + var rightFirst = b.JoinForFlowAnalysisBranches(a, isPossiblyNullableReferenceTypeTypeParameter, identity); + Assert.Equal(leftFirst, rightFirst); + } + } + } + } + + [Fact] + public void TestMeetForFixingUpperBoundsIsCommutative() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + var leftFirst = a.MeetForFixingUpperBounds(b); + var rightFirst = b.MeetForFixingUpperBounds(a); + Assert.Equal(leftFirst, rightFirst); + } + } + } + + [Fact] + public void TestMeetForFlowAnalysisFinallyIsCommutative() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (var c in s_AllNullableAnnotations) + { + var leftFirst = a.MeetForFlowAnalysisFinally(b); + var rightFirst = b.MeetForFlowAnalysisFinally(a); + Assert.Equal(leftFirst, rightFirst); + } + } + } + } + + [Fact] + public void TestEnsureCompatibleIsCommutative() + { + Func identity = x => x; + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (bool isPossiblyNullableReferenceTypeTypeParameter in new[] { true, false }) + { + var leftFirst = a.EnsureCompatible(b, isPossiblyNullableReferenceTypeTypeParameter, identity, out var w1); + var rightFirst = b.EnsureCompatible(a, isPossiblyNullableReferenceTypeTypeParameter, identity, out var w2); + Assert.Equal(leftFirst, rightFirst); + Assert.Equal(w1, w2); + } + } + } + } + + [Fact(Skip ="Two different implementations of NullableAnnotation Join do not agree")] + public void TestJoinsAgree() + { + Func identity = x => x; + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + foreach (bool isPossiblyNullableReferenceTypeTypeParameter in new[] { true, false }) + { + var result1 = a.JoinForFixingLowerBounds(b); + var result2 = a.JoinForFlowAnalysisBranches(b, isPossiblyNullableReferenceTypeTypeParameter, identity); + Assert.Equal(result1, result2); + } + } + } + } + + [Fact(Skip = "Two different implementations of NullableAnnotation Meet do not agree")] + public void TestMeetsAgree() + { + foreach (var a in s_AllNullableAnnotations) + { + foreach (var b in s_AllNullableAnnotations) + { + var result1 = a.MeetForFixingUpperBounds(b); + var result2 = a.MeetForFlowAnalysisFinally(b); + Assert.Equal(result1, result2); + } + } + } } }