Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 2 additions & 107 deletions src/Analysis/Ast/Impl/Analyzer/Handlers/ConditionalHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,123 +13,18 @@
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System;
using System.Linq;
using Microsoft.Python.Core.OS;
using Microsoft.Python.Parsing;
using Microsoft.Python.Parsing.Ast;

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

private enum ConditionTestResult {
Unrecognized,
DontWalkBody,
WalkBody
}

public ConditionalHandler(AnalysisWalker walker) : base(walker) {
_platformService = Eval.Services.GetService<IOSPlatform>();
}

public bool HandleIf(IfStatement node) {
// System version, platform and os.path specializations
var someRecognized = false;
foreach (var test in node.Tests) {
var result = TryHandleSysVersionInfo(test);
if (result != ConditionTestResult.Unrecognized) {
if (result == ConditionTestResult.WalkBody) {
test.Walk(Walker);
}
someRecognized = true;
continue;
}

result = TryHandleSysPlatform(test);
if (result != ConditionTestResult.Unrecognized) {
if (result == ConditionTestResult.WalkBody) {
test.Walk(Walker);
}
someRecognized = true;
continue;
}

result = TryHandleOsPath(test);
if (result != ConditionTestResult.Unrecognized) {
if (result == ConditionTestResult.WalkBody) {
test.Walk(Walker);
return false; // Execute only one condition.
}
someRecognized = true;
}
}
return !someRecognized;
}

private ConditionTestResult TryHandleSysVersionInfo(IfStatementTest test) {
if (test.Test is BinaryExpression cmp &&
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "version_info" &&
cmp.Right is TupleExpression te && te.Items.All(i => (i as ConstantExpression)?.Value is int)) {
Version v;
try {
v = new Version(
(int)((te.Items.ElementAtOrDefault(0) as ConstantExpression)?.Value ?? 0),
(int)((te.Items.ElementAtOrDefault(1) as ConstantExpression)?.Value ?? 0)
);
} catch (ArgumentException) {
// Unsupported comparison, so walk all children
return ConditionTestResult.WalkBody;
}

var shouldWalk = false;
switch (cmp.Operator) {
case PythonOperator.LessThan:
shouldWalk = Ast.LanguageVersion.ToVersion() < v;
break;
case PythonOperator.LessThanOrEqual:
shouldWalk = Ast.LanguageVersion.ToVersion() <= v;
break;
case PythonOperator.GreaterThan:
shouldWalk = Ast.LanguageVersion.ToVersion() > v;
break;
case PythonOperator.GreaterThanOrEqual:
shouldWalk = Ast.LanguageVersion.ToVersion() >= v;
break;
}
return shouldWalk ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
}
return ConditionTestResult.Unrecognized;
}

private ConditionTestResult TryHandleSysPlatform(IfStatementTest test) {
if (test.Test is BinaryExpression cmp &&
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "platform" &&
cmp.Right is ConstantExpression cex && cex.GetStringValue() is string s) {
switch (cmp.Operator) {
case PythonOperator.Equals:
return s == "win32" && _platformService.IsWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
case PythonOperator.NotEquals:
return s == "win32" && _platformService.IsWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
}
return ConditionTestResult.DontWalkBody;
}
return ConditionTestResult.Unrecognized;
}

private ConditionTestResult TryHandleOsPath(IfStatementTest test) {
if (test.Test is BinaryExpression cmp &&
cmp.Left is ConstantExpression cex && cex.GetStringValue() is string s &&
cmp.Right is NameExpression nex && nex.Name == "_names") {
switch (cmp.Operator) {
case PythonOperator.In when s == "nt":
return _platformService.IsWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
case PythonOperator.In when s == "posix":
return _platformService.IsWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
}
return ConditionTestResult.DontWalkBody;
}
return ConditionTestResult.Unrecognized;
}
public bool HandleIf(IfStatement node)
=> node.WalkIfWithSystemConditions(Walker, Ast.LanguageVersion, _platformService.IsWindows);
}
}
5 changes: 4 additions & 1 deletion src/Analysis/Ast/Impl/Analyzer/Symbols/SymbolCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Python.Analysis.Analyzer.Evaluation;
using Microsoft.Python.Analysis.Diagnostics;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Analysis.Values;
using Microsoft.Python.Core;
using Microsoft.Python.Core.OS;
using Microsoft.Python.Parsing.Ast;

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

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

public override bool Walk(IfStatement node)
=> node.WalkIfWithSystemConditions(this, _eval.Ast.LanguageVersion, _eval.Services.GetService<IOSPlatform>().IsWindows);

public override bool Walk(ClassDefinition cd) {
if (IsDeprecated(cd)) {
return false;
Expand Down
94 changes: 94 additions & 0 deletions src/Analysis/Ast/Impl/Extensions/IfStatementExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using System;
using System.Linq;
using Microsoft.Python.Parsing;
using Microsoft.Python.Parsing.Ast;

namespace Microsoft.Python.Analysis {
public enum ConditionTestResult {
Unrecognized,
DontWalkBody,
WalkBody
}

public static class IfStatementExtensions {
public static ConditionTestResult TryHandleSysVersionInfo(this IfStatementTest test, PythonLanguageVersion languageVersion) {
if (test.Test is BinaryExpression cmp &&
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "version_info" &&
cmp.Right is TupleExpression te && te.Items.All(i => (i as ConstantExpression)?.Value is int)) {
Version v;
try {
v = new Version(
(int)((te.Items.ElementAtOrDefault(0) as ConstantExpression)?.Value ?? 0),
(int)((te.Items.ElementAtOrDefault(1) as ConstantExpression)?.Value ?? 0)
);
} catch (ArgumentException) {
// Unsupported comparison, so walk all children
return ConditionTestResult.WalkBody;
}

var shouldWalk = false;
switch (cmp.Operator) {
case PythonOperator.LessThan:
shouldWalk = languageVersion.ToVersion() < v;
break;
case PythonOperator.LessThanOrEqual:
shouldWalk = languageVersion.ToVersion() <= v;
break;
case PythonOperator.GreaterThan:
shouldWalk = languageVersion.ToVersion() > v;
break;
case PythonOperator.GreaterThanOrEqual:
shouldWalk = languageVersion.ToVersion() >= v;
break;
}
return shouldWalk ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
}
return ConditionTestResult.Unrecognized;
}

public static ConditionTestResult TryHandleSysPlatform(this IfStatementTest test, bool isWindows) {
if (test.Test is BinaryExpression cmp &&
cmp.Left is MemberExpression me && (me.Target as NameExpression)?.Name == "sys" && me.Name == "platform" &&
cmp.Right is ConstantExpression cex && cex.GetStringValue() is string s) {
switch (cmp.Operator) {
case PythonOperator.Equals:
return s == "win32" && isWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
case PythonOperator.NotEquals:
return s == "win32" && isWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
}
return ConditionTestResult.DontWalkBody;
}
return ConditionTestResult.Unrecognized;
}

public static ConditionTestResult TryHandleOsPath(this IfStatementTest test, bool isWindows) {
if (test.Test is BinaryExpression cmp &&
cmp.Left is ConstantExpression cex && cex.GetStringValue() is string s &&
cmp.Right is NameExpression nex && nex.Name == "_names") {
switch (cmp.Operator) {
case PythonOperator.In when s == "nt":
return isWindows ? ConditionTestResult.WalkBody : ConditionTestResult.DontWalkBody;
case PythonOperator.In when s == "posix":
return isWindows ? ConditionTestResult.DontWalkBody : ConditionTestResult.WalkBody;
}
return ConditionTestResult.DontWalkBody;
}
return ConditionTestResult.Unrecognized;
}
}
}
61 changes: 61 additions & 0 deletions src/Analysis/Ast/Impl/Extensions/PythonWalkerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright(c) Microsoft Corporation
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the License); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABILITY OR NON-INFRINGEMENT.
//
// See the Apache Version 2.0 License for specific language governing
// permissions and limitations under the License.

using Microsoft.Python.Parsing;
using Microsoft.Python.Parsing.Ast;

namespace Microsoft.Python.Analysis {
public static class PythonWalkerExtensions {
public static bool WalkIfWithSystemConditions(this IfStatement node, PythonWalker walker, PythonLanguageVersion languageVersion, bool isWindows) {
// System version, platform and os.path specializations
var executeElse = false;
foreach (var test in node.Tests) {

var result = test.TryHandleSysVersionInfo(languageVersion);
if (result == ConditionTestResult.Unrecognized) {
result = test.TryHandleSysPlatform(isWindows);
if (result == ConditionTestResult.Unrecognized) {
result = test.TryHandleOsPath(isWindows);
}
}

// If condition is satisfied, walk the corresponding block and
// return false indicating that statement should not be walked again.
// If condition is false or was not recognized, continue but remember
// if we need to execute final else clause.
switch (result) {
case ConditionTestResult.WalkBody:
test.Walk(walker);
return false; // We only need to execute one of the clauses.
case ConditionTestResult.DontWalkBody:
// If condition is false, continue but remember
// if we may need to execute the final else clause.
executeElse = true;
break;
case ConditionTestResult.Unrecognized:
continue; // See if other conditions may work.
}
}

if (executeElse) {
node.ElseStatement?.Walk(walker);
return false;
}

// We didn't walk anything, so me caller do their own thing.
return true;
}
}
}
49 changes: 38 additions & 11 deletions src/Analysis/Ast/Test/ConditionalsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
// permissions and limitations under the License.

using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Python.Analysis.Tests.FluentAssertions;
using Microsoft.Python.Analysis.Types;
using Microsoft.Python.Core.IO;
Expand Down Expand Up @@ -76,15 +75,13 @@ public async Task SysPlatformWindows(bool isWindows) {
const string code = @"
if sys.platform == 'win32':
x = 1
else:
x = 'a'
";
var platform = SubstitutePlatform(out var sm);
platform.IsWindows.Returns(x => isWindows);
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X, sm);
if (isWindows) {
analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.Int);
} else {
analysis.Should().NotHaveVariable("x");
}
analysis.Should().HaveVariable("x").OfType(isWindows ? BuiltinTypeId.Int : BuiltinTypeId.Str);
}

[DataRow(false)]
Expand Down Expand Up @@ -130,15 +127,45 @@ public async Task OsPathPosix(bool isWindows) {
const string code = @"
if 'posix' in _names:
x = 1
else:
x = 'a'
";
var platform = SubstitutePlatform(out var sm);
platform.IsWindows.Returns(x => isWindows);
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable2X, sm);
if (!isWindows) {
analysis.Should().HaveVariable("x").OfType(BuiltinTypeId.Int);
} else {
analysis.Should().NotHaveVariable("x");
}
analysis.Should().HaveVariable("x").OfType(isWindows ? BuiltinTypeId.Str : BuiltinTypeId.Int);
}

[DataRow(false)]
[DataRow(true)]
[DataTestMethod, Priority(0)]
public async Task FunctionByVersion(bool is3x) {
const string code = @"
if sys.version_info >= (3, 0):
def func(a): ...
else:
def func(a, b): ...
";
var analysis = await GetAnalysisAsync(code, is3x ? PythonVersions.LatestAvailable3X : PythonVersions.LatestAvailable2X);
analysis.Should().HaveFunction("func")
.Which.Should().HaveSingleOverload()
.Which.Should().HaveParameters(is3x ? new[] { "a" } : new[] { "a", "b" });
}

[DataRow(false)]
[DataRow(true)]
[DataTestMethod, Priority(0)]
public async Task FunctionByVersionElif(bool is3x) {
const string code = @"
if sys.version_info >= (3, 0):
def func(a): ...
elif sys.version_info < (3, 0):
def func(a, b): ...
";
var analysis = await GetAnalysisAsync(code, is3x ? PythonVersions.LatestAvailable3X : PythonVersions.LatestAvailable2X);
analysis.Should().HaveFunction("func")
.Which.Should().HaveSingleOverload()
.Which.Should().HaveParameters(is3x ? new[] { "a" } : new[] { "a", "b" });
}

private IOSPlatform SubstitutePlatform(out IServiceManager sm) {
Expand Down