Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit d4e2799

Browse files
author
Mikhail Arkhipov
authored
Prefer type from parameter annotation to the type of default value (#1159)
* Treat Any return as unknown * Reverse condition * Prefer annotation to default value * Restore some code * Merge issue
1 parent 6cc5470 commit d4e2799

File tree

3 files changed

+69
-26
lines changed

3 files changed

+69
-26
lines changed

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

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,35 +51,14 @@ public override void Evaluate() {
5151
|| Module.ModuleType == ModuleType.Specialized;
5252

5353
using (Eval.OpenScope(_function.DeclaringModule, FunctionDefinition, out _)) {
54-
// Process annotations.
55-
var annotationType = Eval.GetTypeFromAnnotation(FunctionDefinition.ReturnAnnotation);
56-
if (!annotationType.IsUnknown()) {
57-
// Annotations are typically types while actually functions return
58-
// instances unless specifically annotated to a type such as Type[T].
59-
var t = annotationType.CreateInstance(annotationType.Name, ArgumentSet.Empty);
60-
// If instance could not be create, such as when return type is List[T] and
61-
// type of T is not yet known, just use the type.
62-
var instance = t.IsUnknown() ? annotationType : t;
63-
_overload.SetReturnValue(instance, true);
64-
} else {
65-
// Check if function is a generator
66-
var suite = FunctionDefinition.Body as SuiteStatement;
67-
var yieldExpr = suite?.Statements.OfType<ExpressionStatement>().Select(s => s.Expression as YieldExpression).ExcludeDefault().FirstOrDefault();
68-
if (yieldExpr != null) {
69-
// Function return is an iterator
70-
var yieldValue = Eval.GetValueFromExpression(yieldExpr.Expression) ?? Eval.UnknownType;
71-
var returnValue = new PythonGenerator(Eval.Interpreter, yieldValue);
72-
_overload.SetReturnValue(returnValue, true);
73-
}
74-
}
75-
54+
var returnType = TryDetermineReturnValue();
7655
DeclareParameters(!stub);
7756

7857
// Do process body of constructors since they may be declaring
7958
// variables that are later used to determine return type of other
8059
// methods and properties.
8160
var ctor = _function.Name.EqualsOrdinal("__init__") || _function.Name.EqualsOrdinal("__new__");
82-
if (ctor || annotationType.IsUnknown() || Module.ModuleType == ModuleType.User) {
61+
if (ctor || returnType.IsUnknown() || Module.ModuleType == ModuleType.User) {
8362
// Return type from the annotation is sufficient for libraries and stubs, no need to walk the body.
8463
FunctionDefinition.Body?.Walk(this);
8564
// For libraries remove declared local function variables to free up some memory.
@@ -129,6 +108,30 @@ public override bool Walk(FunctionDefinition node) {
129108
return false;
130109
}
131110

111+
private IPythonType TryDetermineReturnValue() {
112+
var annotationType = Eval.GetTypeFromAnnotation(FunctionDefinition.ReturnAnnotation);
113+
if (!annotationType.IsUnknown()) {
114+
// Annotations are typically types while actually functions return
115+
// instances unless specifically annotated to a type such as Type[T].
116+
var t = annotationType.CreateInstance(annotationType.Name, ArgumentSet.Empty);
117+
// If instance could not be created, such as when return type is List[T] and
118+
// type of T is not yet known, just use the type.
119+
var instance = t.IsUnknown() ? annotationType : t;
120+
_overload.SetReturnValue(instance, true); _overload.SetReturnValue(instance, true);
121+
} else {
122+
// Check if function is a generator
123+
var suite = FunctionDefinition.Body as SuiteStatement;
124+
var yieldExpr = suite?.Statements.OfType<ExpressionStatement>().Select(s => s.Expression as YieldExpression).ExcludeDefault().FirstOrDefault();
125+
if (yieldExpr != null) {
126+
// Function return is an iterator
127+
var yieldValue = Eval.GetValueFromExpression(yieldExpr.Expression) ?? Eval.UnknownType;
128+
var returnValue = new PythonGenerator(Eval.Interpreter, yieldValue);
129+
_overload.SetReturnValue(returnValue, true);
130+
}
131+
}
132+
return annotationType;
133+
}
134+
132135
private void DeclareParameters(bool declareVariables) {
133136
// For class method no need to add extra parameters, but first parameter type should be the class.
134137
// For static and unbound methods do not add or set anything.
@@ -157,8 +160,8 @@ private void DeclareParameters(bool declareVariables) {
157160
for (var i = skip; i < FunctionDefinition.Parameters.Length; i++) {
158161
var p = FunctionDefinition.Parameters[i];
159162
if (!string.IsNullOrEmpty(p.Name)) {
160-
IPythonType paramType = null;
161-
if (p.DefaultValue != null) {
163+
var paramType = Eval.GetTypeFromAnnotation(p.Annotation);
164+
if (paramType.IsUnknown() && p.DefaultValue != null) {
162165
defaultValue = Eval.GetValueFromExpression(p.DefaultValue);
163166
// If parameter has default value, look for the annotation locally first
164167
// since outer type may be getting redefined. Consider 's = None; def f(s: s = 123): ...

src/Analysis/Ast/Test/FunctionTests.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16-
using System;
1716
using System.IO;
1817
using System.Linq;
1918
using System.Threading.Tasks;
@@ -589,5 +588,24 @@ def b(self): pass
589588
analysis.Should().NotHaveVariable("func");
590589
analysis.Should().HaveVariable("B").Which.Should().NotHaveMember("b");
591590
}
591+
592+
[TestMethod, Priority(0)]
593+
public async Task AnnotatedParameterPriority() {
594+
const string code = @"
595+
class Foo:
596+
def __init__(self, name: str):
597+
self.name = name
598+
599+
def func() -> int:
600+
return Foo
601+
602+
def test(foo: Foo = func()):
603+
return foo
604+
605+
x = test()
606+
";
607+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
608+
analysis.Should().HaveVariable("x").OfType("Foo");
609+
}
592610
}
593611
}

src/LanguageServer/Test/CompletionTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,5 +1172,27 @@ def func(self):
11721172
result = cs.GetCompletions(analysis, new SourceLocation(9, 5));
11731173
result.Completions.Select(c => c.label).Should().NotContain("_A__x").And.NotContain("__x");
11741174
}
1175+
1176+
[TestMethod, Priority(0)]
1177+
public async Task ParameterAnnotatedDefault() {
1178+
const string code = @"
1179+
from typing import Any
1180+
1181+
class Foo:
1182+
z: int
1183+
def __init__(self, name: str):
1184+
self.name = name
1185+
1186+
def func() -> Any:
1187+
return 123
1188+
1189+
def test(x: Foo = func()):
1190+
x.
1191+
";
1192+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
1193+
var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion);
1194+
var comps = cs.GetCompletions(analysis, new SourceLocation(13, 7));
1195+
comps.Should().HaveLabels("name", "z");
1196+
}
11751197
}
11761198
}

0 commit comments

Comments
 (0)