/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Emit3/FlowAnalysis/PatternsVsRegions.cs
710 строк
28 KB
AlekseyTs
Unions: Address remaining PROTOTYPE comments (#84499)
16 июл 2026, 17:17
Не верифицирован
16 июл 2026, 17:17
8c386fe
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { /// <summary> /// Region analysis tests involving pattern-matching constructs. /// </summary> public partial class PatternsVsRegions : FlowTestBase { [Fact] public void RegionInIsPattern01() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression( @"class Program { public static void Main(string[] args) { object o = args; if (/*<bind>*/o is int i && i > 10/*</bind>*/) {} } }"); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); Assert.Equal("o", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); Assert.Equal("o, i", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); Assert.Equal("args", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); Assert.Equal("args, o", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.UnsafeAddressTaken)); } [Fact] public void RegionInIsPattern02() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression( @"class Program { public static void Main(string[] args) { object o = args; if (/*<bind>*/o is int i/*</bind>*/ && i > 10) {} } }"); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); Assert.Equal("o", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); Assert.Equal("o", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); Assert.Equal("args, i", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); Assert.Equal("args, o", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.UnsafeAddressTaken)); } [Fact] public void RegionInIsPattern03() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression( @"class Program { public static void Main(string[] args) { object o = args; if (o is int i && /*<bind>*/i > 10/*</bind>*/) {} } }"); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); Assert.Equal("args, o", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); Assert.Equal("args, o, i", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.UnsafeAddressTaken)); } [Fact] public void RegionInIsPattern04() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression( @"class Program { public static void Main(string[] args) { int o = args.Length; if (/*<bind>*/o is int i/*</bind>*/ && i > 10) {} } }"); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); Assert.Equal("o", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); Assert.Equal("o", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); Assert.Equal("args, i", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); Assert.Equal("args, o", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.UnsafeAddressTaken)); } [Fact] public void RegionInIsPattern05() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression( @"class Program { public static void Main(string[] args) { if (/*<bind>*/args is [] i/*</bind>*/) {} } }"); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); Assert.Equal("args", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); Assert.Equal("args", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); Assert.Equal("i", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); Assert.Equal("args", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.UnsafeAddressTaken)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79489")] public void RegionInIsPattern06() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression(""" C.Use(doLogic: true); class C { public static (bool, string? errorMessage) M() => (false, "Something went wrong"); public static void Use(bool doLogic) { if (doLogic) { if (/*<bind>*/M() is (false, var errorMessage)/*</bind>*/) { Console.Error.WriteLine(errorMessage); } } } } """); VerifyDataFlowAnalysis(""" VariablesDeclared: errorMessage AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: DataFlowsOut: errorMessage DefinitelyAssignedOnEntry: doLogic DefinitelyAssignedOnExit: doLogic ReadInside: ReadOutside: doLogic, errorMessage WrittenInside: errorMessage WrittenOutside: doLogic """, dataFlowAnalysisResults); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79489")] public void RegionInSwitch01() { var comp = CreateCompilation(""" static void PatternMatchingUsage() { var t = (1, 2); // selection start switch (t) { case (var x, 2): Console.WriteLine(x); break; } // selection end } """); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var @switch = tree.GetRoot().DescendantNodes().OfType<SwitchStatementSyntax>().Single(); var dataFlowAnalysisResults = model.AnalyzeDataFlow(@switch); VerifyDataFlowAnalysis(""" VariablesDeclared: x AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: t DataFlowsOut: DefinitelyAssignedOnEntry: t, args DefinitelyAssignedOnExit: t, args ReadInside: t, x ReadOutside: WrittenInside: x WrittenOutside: t, args """, dataFlowAnalysisResults); } [Fact] public void RegionInIsPattern07() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression(""" C.Use(doLogic: true); class C { public static (bool, (string result, string? errorMessage)) M() => (false, "Something went wrong"); public static void Use(bool doLogic) { if (doLogic) { if (/*<bind>*/M() is (false, (var inner, var errorMessage))/*</bind>*/) { Console.Error.WriteLine(errorMessage); } } } } """); VerifyDataFlowAnalysis(""" VariablesDeclared: inner, errorMessage AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: DataFlowsOut: errorMessage DefinitelyAssignedOnEntry: doLogic DefinitelyAssignedOnExit: doLogic ReadInside: ReadOutside: doLogic, errorMessage WrittenInside: inner, errorMessage WrittenOutside: doLogic """, dataFlowAnalysisResults); } [Fact] public void RegionInIsPattern08() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression(""" C.Use(doLogic: true); class C { public static (string result, (bool, string? errorMessage)) M() => (false, "Something went wrong"); public static void Use(bool doLogic) { if (doLogic) { if (/*<bind>*/M() is (var outer, (true, var errorMessage) tuple)/*</bind>*/) { Console.Error.WriteLine(errorMessage); } } } } """); VerifyDataFlowAnalysis(""" VariablesDeclared: outer, errorMessage, tuple AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: DataFlowsOut: errorMessage DefinitelyAssignedOnEntry: doLogic DefinitelyAssignedOnExit: doLogic ReadInside: ReadOutside: doLogic, errorMessage WrittenInside: outer, errorMessage, tuple WrittenOutside: doLogic """, dataFlowAnalysisResults); } [Fact] public void RegionInIsPattern09() { var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression(""" C.Use(doLogic: true); class C { public static (string result, (bool, string? errorMessage)) M() => (false, "Something went wrong"); public static void Use(bool doLogic) { if (doLogic) { if (/*<bind>*/M() is { result: var outer, Item2: { errorMessage: var errorMessage } tuple })/*</bind>*/) { Console.Error.WriteLine(errorMessage); } } } } """); VerifyDataFlowAnalysis(""" VariablesDeclared: outer, errorMessage, tuple AlwaysAssigned: outer, errorMessage, tuple Captured: CapturedInside: CapturedOutside: DataFlowsIn: DataFlowsOut: errorMessage DefinitelyAssignedOnEntry: doLogic DefinitelyAssignedOnExit: doLogic, outer, errorMessage, tuple ReadInside: ReadOutside: doLogic, errorMessage WrittenInside: outer, errorMessage, tuple WrittenOutside: doLogic """, dataFlowAnalysisResults); } [Fact] public void RegionWithinSubpattern01() { var comp = CreateCompilation(""" int x = 1; if (x is var y and 2) { } """); comp.VerifyDiagnostics(); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var constantPattern = tree.GetRoot().DescendantNodes().OfType<ConstantPatternSyntax>().Single(); Assert.Equal("2", constantPattern.ToString()); Assert.Throws<ArgumentException>(() => model.AnalyzeDataFlow(constantPattern)); var dataFlow = model.AnalyzeDataFlow(constantPattern.Expression); VerifyDataFlowAnalysis(""" VariablesDeclared: AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: DataFlowsOut: DefinitelyAssignedOnEntry: x, y, args DefinitelyAssignedOnExit: x, y, args ReadInside: ReadOutside: x WrittenInside: WrittenOutside: x, y, args """, dataFlow); } [Fact] public void RegionWithinSubpattern02() { var comp = CreateCompilation(""" static class Program { static void Main() { int x = 1; if (x is var y and x.P and > 2) { } } extension (ref int i) { public int P => i; } } """); comp.VerifyDiagnostics( // (6,28): error CS9135: A constant value of type 'int' is expected // if (x is var y and x.P and > 2) { } Diagnostic(ErrorCode.ERR_ConstantValueOfTypeExpected, "x.P").WithArguments("int").WithLocation(6, 28)); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var constantPattern = tree.GetRoot().DescendantNodes().OfType<ConstantPatternSyntax>().Single(); Assert.Equal("x.P", constantPattern.ToString()); Assert.Throws<ArgumentException>(() => model.AnalyzeDataFlow(constantPattern)); var dataFlow = model.AnalyzeDataFlow(constantPattern.Expression); VerifyDataFlowAnalysis(""" VariablesDeclared: AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: x DataFlowsOut: DefinitelyAssignedOnEntry: x, y DefinitelyAssignedOnExit: x, y ReadInside: x ReadOutside: x WrittenInside: WrittenOutside: x, y """, dataFlow); } [Fact] public void RegionWithinSubpattern03() { var comp = CreateCompilation(""" bool x = false; if (x is var y and (x is var z)) { } """); comp.VerifyDiagnostics( // (2,21): error CS9135: A constant value of type 'bool' is expected // if (x is var y and (x is var z)) { } Diagnostic(ErrorCode.ERR_ConstantValueOfTypeExpected, "x is var z").WithArguments("bool").WithLocation(2, 21)); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var constantPattern = tree.GetRoot().DescendantNodes().OfType<ConstantPatternSyntax>().Single(); Assert.Equal("(x is var z)", constantPattern.ToString()); Assert.Throws<ArgumentException>(() => model.AnalyzeDataFlow(constantPattern)); var dataFlow = model.AnalyzeDataFlow(constantPattern.Expression); VerifyDataFlowAnalysis(""" VariablesDeclared: z AlwaysAssigned: z Captured: CapturedInside: CapturedOutside: DataFlowsIn: x DataFlowsOut: DefinitelyAssignedOnEntry: x, y, args DefinitelyAssignedOnExit: x, y, z, args ReadInside: x ReadOutside: x WrittenInside: z WrittenOutside: x, y, args """, dataFlow); } [Fact] public void RegionITuplePattern() { var source = """ class Program { static bool M(object obj) { return obj is (object x, string y, int z); } } """; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); compilation.VerifyEmitDiagnostics(); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); Assert.Equal("obj is (object x, string y, int z)", node.ToString()); VerifyDataFlowAnalysis(""" VariablesDeclared: x, y, z AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: obj DataFlowsOut: DefinitelyAssignedOnEntry: obj DefinitelyAssignedOnExit: obj ReadInside: obj ReadOutside: WrittenInside: x, y, z WrittenOutside: obj """, model.AnalyzeDataFlow(node)); } [Fact] public void RegionListPattern() { var source = """ class Program { static bool M(object[] arr) { return arr is [object x, string y, int z] arr1; } } """; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); compilation.VerifyEmitDiagnostics(); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); Assert.Equal("arr is [object x, string y, int z] arr1", node.ToString()); VerifyDataFlowAnalysis(""" VariablesDeclared: x, y, z, arr1 AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: arr DataFlowsOut: DefinitelyAssignedOnEntry: arr DefinitelyAssignedOnExit: arr ReadInside: arr ReadOutside: WrittenInside: x, y, z, arr1 WrittenOutside: arr """, model.AnalyzeDataFlow(node)); } [Fact] public void RegionSlicePattern() { var source = """ class Program { static bool M(object[] arr) { return arr is [object x, ..object[] y]; } } """; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); compilation.VerifyEmitDiagnostics(); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); Assert.Equal("arr is [object x, ..object[] y]", node.ToString()); VerifyDataFlowAnalysis(""" VariablesDeclared: x, y AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: arr DataFlowsOut: DefinitelyAssignedOnEntry: arr DefinitelyAssignedOnExit: arr ReadInside: arr ReadOutside: WrittenInside: x, y WrittenOutside: arr """, model.AnalyzeDataFlow(node)); } [Fact] public void RegionNotPattern() { var source = """ class Program { static void M(object obj) { if (obj is not string s) { } } } """; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); compilation.VerifyEmitDiagnostics(); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); Assert.Equal("obj is not string s", node.ToString()); VerifyDataFlowAnalysis(""" VariablesDeclared: s AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: obj DataFlowsOut: DefinitelyAssignedOnEntry: obj DefinitelyAssignedOnExit: obj ReadInside: obj ReadOutside: WrittenInside: s WrittenOutside: obj """, model.AnalyzeDataFlow(node)); } [Fact] public void RegionOrPattern() { var source = """ class Program { static void M(object obj) { if (obj is string s or int i) { } } } """; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); compilation.VerifyEmitDiagnostics( // (5,27): error CS8780: A variable may not be declared within a 'not' or an 'or' pattern or a union matching involving matching against either the instance, or its underlying value. // if (obj is string s or int i) Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "s").WithLocation(5, 27), // (5,36): error CS8780: A variable may not be declared within a 'not' or an 'or' pattern or a union matching involving matching against either the instance, or its underlying value. // if (obj is string s or int i) Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "i").WithLocation(5, 36)); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); Assert.Equal("obj is string s or int i", node.ToString()); VerifyDataFlowAnalysis(""" VariablesDeclared: s, i AlwaysAssigned: Captured: CapturedInside: CapturedOutside: DataFlowsIn: obj DataFlowsOut: DefinitelyAssignedOnEntry: obj DefinitelyAssignedOnExit: obj ReadInside: obj ReadOutside: WrittenInside: WrittenOutside: obj """, model.AnalyzeDataFlow(node)); } [Fact] public void RegionSplitState() { var source = """ class Program { static void M(object obj) { if (obj is var s) { throw null!; } return; } } """; var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); compilation.VerifyEmitDiagnostics(); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); Assert.Equal("obj is var s", node.ToString()); VerifyDataFlowAnalysis(""" VariablesDeclared: s AlwaysAssigned: s Captured: CapturedInside: CapturedOutside: DataFlowsIn: obj DataFlowsOut: DefinitelyAssignedOnEntry: obj DefinitelyAssignedOnExit: obj, s ReadInside: obj ReadOutside: WrittenInside: s WrittenOutside: obj """, model.AnalyzeDataFlow(node)); } } }