Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit 28f5b6c

Browse files
author
MikhailArkhipov
committed
Handle else in platform and version clauses + handle it in symbol collector
1 parent 5641e5b commit 28f5b6c

File tree

5 files changed

+182
-117
lines changed

5 files changed

+182
-117
lines changed

src/Analysis/Ast/Impl/Analyzer/Handlers/ConditionalHandler.cs

Lines changed: 2 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -13,123 +13,18 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16-
using System;
17-
using System.Linq;
1816
using Microsoft.Python.Core.OS;
19-
using Microsoft.Python.Parsing;
2017
using Microsoft.Python.Parsing.Ast;
2118

2219
namespace Microsoft.Python.Analysis.Analyzer.Handlers {
2320
internal sealed class ConditionalHandler : StatementHandler {
2421
private readonly IOSPlatform _platformService;
2522

26-
private enum ConditionTestResult {
27-
Unrecognized,
28-
DontWalkBody,
29-
WalkBody
30-
}
31-
3223
public ConditionalHandler(AnalysisWalker walker) : base(walker) {
3324
_platformService = Eval.Services.GetService<IOSPlatform>();
3425
}
3526

36-
public bool HandleIf(IfStatement node) {
37-
// System version, platform and os.path specializations
38-
var someRecognized = false;
39-
foreach (var test in node.Tests) {
40-
var result = TryHandleSysVersionInfo(test);
41-
if (result != ConditionTestResult.Unrecognized) {
42-
if (result == ConditionTestResult.WalkBody) {
43-
test.Walk(Walker);
44-
}
45-
someRecognized = true;
46-
continue;
47-
}
48-
49-
result = TryHandleSysPlatform(test);
50-
if (result != ConditionTestResult.Unrecognized) {
51-
if (result == ConditionTestResult.WalkBody) {
52-
test.Walk(Walker);
53-
}
54-
someRecognized = true;
55-
continue;
56-
}
57-
58-
result = TryHandleOsPath(test);
59-
if (result != ConditionTestResult.Unrecognized) {
60-
if (result == ConditionTestResult.WalkBody) {
61-
test.Walk(Walker);
62-
return false; // Execute only one condition.
63-
}
64-
someRecognized = true;
65-
}
66-
}
67-
return !someRecognized;
68-
}
69-
70-
private ConditionTestResult TryHandleSysVersionInfo(IfStatementTest test) {
71-
if (test.Test is BinaryExpression cmp &&
72-
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "version_info" &&
73-
cmp.Right is TupleExpression te && te.Items.All(i => (i as ConstantExpression)?.Value is int)) {
74-
Version v;
75-
try {
76-
v = new Version(
77-
(int)((te.Items.ElementAtOrDefault(0) as ConstantExpression)?.Value ?? 0),
78-
(int)((te.Items.ElementAtOrDefault(1) as ConstantExpression)?.Value ?? 0)
79-
);
80-
} catch (ArgumentException) {
81-
// Unsupported comparison, so walk all children
82-
return ConditionTestResult.WalkBody;
83-
}
84-
85-
var shouldWalk = false;
86-
switch (cmp.Operator) {
87-
case PythonOperator.LessThan:
88-
shouldWalk = Ast.LanguageVersion.ToVersion() < v;
89-
break;
90-
case PythonOperator.LessThanOrEqual:
91-
shouldWalk = Ast.LanguageVersion.ToVersion() <= v;
92-
break;
93-
case PythonOperator.GreaterThan:
94-
shouldWalk = Ast.LanguageVersion.ToVersion() > v;
95-
break;
96-
case PythonOperator.GreaterThanOrEqual:
97-
shouldWalk = Ast.LanguageVersion.ToVersion() >= v;
98-
break;
99-
}
100-
return shouldWalk ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
101-
}
102-
return ConditionTestResult.Unrecognized;
103-
}
104-
105-
private ConditionTestResult TryHandleSysPlatform(IfStatementTest test) {
106-
if (test.Test is BinaryExpression cmp &&
107-
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "platform" &&
108-
cmp.Right is ConstantExpression cex && cex.GetStringValue() is string s) {
109-
switch (cmp.Operator) {
110-
case PythonOperator.Equals:
111-
return s == "win32" && _platformService.IsWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
112-
case PythonOperator.NotEquals:
113-
return s == "win32" && _platformService.IsWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
114-
}
115-
return ConditionTestResult.DontWalkBody;
116-
}
117-
return ConditionTestResult.Unrecognized;
118-
}
119-
120-
private ConditionTestResult TryHandleOsPath(IfStatementTest test) {
121-
if (test.Test is BinaryExpression cmp &&
122-
cmp.Left is ConstantExpression cex && cex.GetStringValue() is string s &&
123-
cmp.Right is NameExpression nex && nex.Name == "_names") {
124-
switch (cmp.Operator) {
125-
case PythonOperator.In when s == "nt":
126-
return _platformService.IsWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
127-
case PythonOperator.In when s == "posix":
128-
return _platformService.IsWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
129-
}
130-
return ConditionTestResult.DontWalkBody;
131-
}
132-
return ConditionTestResult.Unrecognized;
133-
}
27+
public bool HandleIf(IfStatement node)
28+
=> node.WalkIfWithSystemConditions(Walker, Ast.LanguageVersion, _platformService.IsWindows);
13429
}
13530
}

src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using Microsoft.Python.Analysis.Types;
2222
using Microsoft.Python.Analysis.Values;
2323
using Microsoft.Python.Core;
24+
using Microsoft.Python.Core.OS;
2425
using Microsoft.Python.Parsing.Ast;
2526

2627
namespace Microsoft.Python.Analysis.Analyzer.Symbols {
@@ -46,6 +47,9 @@ private SymbolCollector(ModuleSymbolTable table, ExpressionEval eval) {
4647

4748
private void Walk() => _eval.Ast.Walk(this);
4849

50+
public override bool Walk(IfStatement node)
51+
=> node.WalkIfWithSystemConditions(this, _eval.Ast.LanguageVersion, _eval.Services.GetService< IOSPlatform>().IsWindows);
52+
4953
public override bool Walk(ClassDefinition cd) {
5054
if (IsDeprecated(cd)) {
5155
return false;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System;
17+
using System.Linq;
18+
using Microsoft.Python.Parsing;
19+
using Microsoft.Python.Parsing.Ast;
20+
21+
namespace Microsoft.Python.Analysis {
22+
public enum ConditionTestResult {
23+
Unrecognized,
24+
DontWalkBody,
25+
WalkBody
26+
}
27+
28+
public static class IfStatementExtensions {
29+
public static ConditionTestResult TryHandleSysVersionInfo(this IfStatementTest test, PythonLanguageVersion languageVersion) {
30+
if (test.Test is BinaryExpression cmp &&
31+
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "version_info" &&
32+
cmp.Right is TupleExpression te && te.Items.All(i => (i as ConstantExpression)?.Value is int)) {
33+
Version v;
34+
try {
35+
v = new Version(
36+
(int)((te.Items.ElementAtOrDefault(0) as ConstantExpression)?.Value ?? 0),
37+
(int)((te.Items.ElementAtOrDefault(1) as ConstantExpression)?.Value ?? 0)
38+
);
39+
} catch (ArgumentException) {
40+
// Unsupported comparison, so walk all children
41+
return ConditionTestResult.WalkBody;
42+
}
43+
44+
var shouldWalk = false;
45+
switch (cmp.Operator) {
46+
case PythonOperator.LessThan:
47+
shouldWalk = languageVersion.ToVersion() < v;
48+
break;
49+
case PythonOperator.LessThanOrEqual:
50+
shouldWalk = languageVersion.ToVersion() <= v;
51+
break;
52+
case PythonOperator.GreaterThan:
53+
shouldWalk = languageVersion.ToVersion() > v;
54+
break;
55+
case PythonOperator.GreaterThanOrEqual:
56+
shouldWalk = languageVersion.ToVersion() >= v;
57+
break;
58+
}
59+
return shouldWalk ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
60+
}
61+
return ConditionTestResult.Unrecognized;
62+
}
63+
64+
public static ConditionTestResult TryHandleSysPlatform(this IfStatementTest test, bool isWindows) {
65+
if (test.Test is BinaryExpression cmp &&
66+
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "platform" &&
67+
cmp.Right is ConstantExpression cex && cex.GetStringValue() is string s) {
68+
switch (cmp.Operator) {
69+
case PythonOperator.Equals:
70+
return s == "win32" && isWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
71+
case PythonOperator.NotEquals:
72+
return s == "win32" && isWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
73+
}
74+
return ConditionTestResult.DontWalkBody;
75+
}
76+
return ConditionTestResult.Unrecognized;
77+
}
78+
79+
public static ConditionTestResult TryHandleOsPath(this IfStatementTest test, bool isWindows) {
80+
if (test.Test is BinaryExpression cmp &&
81+
cmp.Left is ConstantExpression cex && cex.GetStringValue() is string s &&
82+
cmp.Right is NameExpression nex && nex.Name == "_names") {
83+
switch (cmp.Operator) {
84+
case PythonOperator.In when s == "nt":
85+
return isWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
86+
case PythonOperator.In when s == "posix":
87+
return isWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
88+
}
89+
return ConditionTestResult.DontWalkBody;
90+
}
91+
return ConditionTestResult.Unrecognized;
92+
}
93+
}
94+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using Microsoft.Python.Parsing;
17+
using Microsoft.Python.Parsing.Ast;
18+
19+
namespace Microsoft.Python.Analysis {
20+
public static class PythonWalkerExtensions {
21+
public static bool WalkIfWithSystemConditions(this IfStatement node, PythonWalker walker, PythonLanguageVersion languageVersion, bool isWindows) {
22+
// System version, platform and os.path specializations
23+
var someRecognized = false;
24+
foreach (var test in node.Tests) {
25+
var result = test.TryHandleSysVersionInfo(languageVersion);
26+
if (result != ConditionTestResult.Unrecognized) {
27+
if (result == ConditionTestResult.WalkBody) {
28+
test.Walk(walker);
29+
} else {
30+
node.ElseStatement?.Walk(walker);
31+
}
32+
someRecognized = true;
33+
continue;
34+
}
35+
36+
result = test.TryHandleSysPlatform(isWindows);
37+
if (result != ConditionTestResult.Unrecognized) {
38+
if (result == ConditionTestResult.WalkBody) {
39+
test.Walk(walker);
40+
} else {
41+
node.ElseStatement?.Walk(walker);
42+
}
43+
someRecognized = true;
44+
continue;
45+
}
46+
47+
result = test.TryHandleOsPath(isWindows);
48+
if (result != ConditionTestResult.Unrecognized) {
49+
if (result == ConditionTestResult.WalkBody) {
50+
test.Walk(walker);
51+
} else {
52+
node.ElseStatement?.Walk(walker);
53+
}
54+
return false; // Execute only one condition.
55+
}
56+
}
57+
return !someRecognized;
58+
}
59+
}
60+
}

src/Analysis/Ast/Test/ConditionalsTests.cs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,13 @@ public async Task SysPlatformWindows(bool isWindows) {
7676
const string code = @"
7777
if sys.platform == 'win32':
7878
x = 1
79+
else:
80+
x = 'a'
7981
";
8082
var platform = SubstitutePlatform(out var sm);
8183
platform.IsWindows.Returns(x => isWindows);
8284
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X, sm);
83-
if (isWindows) {
84-
analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.Int);
85-
} else {
86-
analysis.Should().NotHaveVariable("x");
87-
}
85+
analysis.Should().HaveVariable("x").OfType(isWindows ? BuiltinTypeId.Int : BuiltinTypeId.Str);
8886
}
8987

9088
[DataRow(false)]
@@ -130,15 +128,29 @@ public async Task OsPathPosix(bool isWindows) {
130128
const string code = @"
131129
if 'posix' in _names:
132130
x = 1
131+
else:
132+
x = 'a'
133133
";
134134
var platform = SubstitutePlatform(out var sm);
135135
platform.IsWindows.Returns(x => isWindows);
136136
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable2X, sm);
137-
if (!isWindows) {
138-
analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.Int);
139-
} else {
140-
analysis.Should().NotHaveVariable("x");
141-
}
137+
analysis.Should().HaveVariable("x").OfType(isWindows ? BuiltinTypeId.Str : BuiltinTypeId.Int);
138+
}
139+
140+
[DataRow(false)]
141+
[DataRow(true)]
142+
[DataTestMethod, Priority(0)]
143+
public async Task FunctionByVersion(bool is3x) {
144+
const string code = @"
145+
if sys.version_info >= (3, 0):
146+
def func(a): ...
147+
else:
148+
def func(a, b): ...
149+
";
150+
var analysis = await GetAnalysisAsync(code, is3x ? PythonVersions.LatestAvailable3X : PythonVersions.LatestAvailable2X);
151+
analysis.Should().HaveFunction("func")
152+
.Which.Should().HaveSingleOverload()
153+
.Which.Should().HaveParameters(is3x ? new[] { "a" } : new[] { "a", "b" });
142154
}
143155

144156
private IOSPlatform SubstitutePlatform(out IServiceManager sm) {

0 commit comments

Comments
 (0)