/// <summary> /// Constructs the null-type flow graph and infers nullabilities for the nodes. /// </summary> public void Analyze(CancellationToken cancellationToken) { Parallel.ForEach(compilation.SyntaxTrees, new ParallelOptions { CancellationToken = cancellationToken }, t => CreateNodes(t, cancellationToken)); Parallel.ForEach(compilation.SyntaxTrees, new ParallelOptions { CancellationToken = cancellationToken }, t => CreateEdges(t, cancellationToken)); MaximumFlow.Compute(typeSystem.AllNodes, typeSystem.NullableNode, typeSystem.NonNullNode, cancellationToken); // Run non-null with ignoreEdgesWithoutCapacity before nullable so that errors // are reported as close to non-null as possible. typeSystem.NonNullNode.NullType = NullType.Infer; InferNonNull(typeSystem.NonNullNode, ignoreEdgesWithoutCapacity: true); typeSystem.NullableNode.NullType = NullType.Infer; InferNullable(typeSystem.NullableNode, ignoreEdgesWithoutCapacity: false); // There's going to be a bunch of remaining nodes where either choice would work. // For parameters, prefer marking those as nullable: foreach (var paramNode in typeSystem.NodesInInputPositions) { InferNullable(paramNode.ReplacedWith); } foreach (var node in typeSystem.AllNodes) { // Finally, anything left over is inferred to be non-null: if (node.NullType == NullType.Infer) { if (node.ReplacedWith.NullType != NullType.Infer) { node.NullType = node.ReplacedWith.NullType; } else { node.NullType = NullType.NonNull; } } Debug.Assert(node.NullType == node.ReplacedWith.NullType); } }
/// <summary> /// Constructs the null-type flow graph and infers nullabilities for the nodes. /// </summary> public void Analyze(ConflictResolutionStrategy strategy, CancellationToken cancellationToken) { Parallel.ForEach(compilation.SyntaxTrees, new ParallelOptions { CancellationToken = cancellationToken }, t => CreateNodes(t, cancellationToken)); Parallel.ForEach(compilation.SyntaxTrees, new ParallelOptions { CancellationToken = cancellationToken }, t => CreateEdges(t, cancellationToken)); switch (strategy) { case ConflictResolutionStrategy.MinimizeWarnings: MaximumFlow.Compute(typeSystem.AllNodes, typeSystem.NullableNode, typeSystem.NonNullNode, cancellationToken); // Infer non-null first using the residual graph. InferNonNullUsingResidualGraph(typeSystem.NonNullNode); // Then use the original graph to infer nullable types everywhere we didn't already infer non-null. // This ends up creating the minimum cut. InferNullable(typeSystem.NullableNode); // Note that for longer chains (null -> A -> B -> C -> nonnull) // this approach ends up cutting the graph as close to nonnull as possible when there's multiple // choices with the same number of warnings. This is why we use the "reverse" residual graph // (ResidualGraphPredecessors) -- using ResidualGraphSuccessors would end up cutting closer to the <null> node. break; case ConflictResolutionStrategy.PreferNull: InferNullable(typeSystem.NullableNode); InferNonNull(typeSystem.NonNullNode); break; case ConflictResolutionStrategy.PreferNotNull: InferNonNull(typeSystem.NonNullNode); InferNullable(typeSystem.NullableNode); break; default: throw new NotSupportedException(strategy.ToString()); } // There's going to be a bunch of remaining nodes where either choice would work. // For parameters, prefer marking those as nullable: foreach (var paramNode in typeSystem.NodesInInputPositions) { if (paramNode.ReplacedWith.NullType == NullType.Infer) { InferNullable(paramNode.ReplacedWith); } } foreach (var node in typeSystem.AllNodes) { // Finally, anything left over is inferred to be non-null: if (node.NullType == NullType.Infer) { if (node.ReplacedWith.NullType != NullType.Infer) { node.NullType = node.ReplacedWith.NullType; } else { node.NullType = NullType.NonNull; } } Debug.Assert(node.NullType == node.ReplacedWith.NullType); } }