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

Commit 138da59

Browse files
author
Mikhail Arkhipov
authored
Completion presentation for provate variables (#1158)
1 parent 91e912c commit 138da59

File tree

4 files changed

+96
-9
lines changed

4 files changed

+96
-9
lines changed

src/Analysis/Ast/Impl/Extensions/PythonClassExtensions.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,47 @@
1717
using Microsoft.Python.Analysis.Analyzer;
1818
using Microsoft.Python.Analysis.Specializations.Typing;
1919
using Microsoft.Python.Analysis.Types;
20+
using Microsoft.Python.Core;
2021

2122
namespace Microsoft.Python.Analysis {
2223
public static class PythonClassExtensions {
23-
public static bool IsGeneric(this IPythonClassType cls)
24+
public static bool IsGeneric(this IPythonClassType cls)
2425
=> cls.Bases != null && cls.Bases.Any(b => b is IGenericType || b is IGenericClassParameter);
2526

2627
public static void AddMemberReference(this IPythonType type, string name, IExpressionEvaluator eval, Location location) {
2728
var m = type.GetMember(name);
2829
if (m is LocatedMember lm) {
2930
lm.AddReference(location);
30-
} else if(type is IPythonClassType cls) {
31+
} else if (type is IPythonClassType cls) {
3132
using (eval.OpenScope(cls)) {
3233
eval.LookupNameInScopes(name, out _, out var v, LookupOptions.Local);
3334
v?.AddReference(location);
3435
}
3536
}
3637
}
38+
39+
/// <summary>
40+
/// Converts mangled name of a private member to its original form,
41+
/// by removing private prefix, such as '_ClassName__x' to '__x'.
42+
/// </summary>
43+
public static string UnmangleMemberName(this IPythonClassType cls, string memberName) {
44+
if (memberName.Length == 0 || (memberName.Length > 0 && memberName[0] != '_')) {
45+
return memberName;
46+
}
47+
48+
var prefix = $"_{cls.Name}__";
49+
return memberName.StartsWithOrdinal(prefix)
50+
? memberName.Substring(cls.Name.Length + 1)
51+
: memberName;
52+
}
53+
54+
/// <summary>
55+
/// Determines if particular member is private by checking its name to mangled form
56+
/// of Python class private members, such as '_ClassName__memberName'.
57+
/// </summary>
58+
public static bool IsPrivateMember(this IPythonClassType cls, string memberName) {
59+
var unmangledName = cls.UnmangleMemberName(memberName);
60+
return unmangledName.StartsWithOrdinal("__") && memberName.EqualsOrdinal($"_{cls.Name}{unmangledName}");
61+
}
3762
}
3863
}

src/LanguageServer/Impl/Completion/CompletionSource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.Linq;
1717
using Microsoft.Python.Analysis;
1818
using Microsoft.Python.Analysis.Analyzer.Expressions;
19+
using Microsoft.Python.Core;
1920
using Microsoft.Python.Core.Text;
2021
using Microsoft.Python.Parsing;
2122
using Microsoft.Python.Parsing.Ast;
@@ -88,7 +89,7 @@ public CompletionResult GetCompletions(IDocumentAnalysis analysis, SourceLocatio
8889
return CompletionResult.Empty;
8990
}
9091
return result == CompletionResult.Empty ? TopLevelCompletion.GetCompletions(statement, scope, context) : result;
91-
}
92+
}
9293
}
9394
}
9495
}

src/LanguageServer/Impl/Completion/ExpressionCompletion.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
using System.Collections.Generic;
1717
using System.Linq;
1818
using Microsoft.Python.Analysis;
19+
using Microsoft.Python.Analysis.Analyzer;
20+
using Microsoft.Python.Analysis.Types;
1921
using Microsoft.Python.Analysis.Values;
22+
using Microsoft.Python.Core;
2023
using Microsoft.Python.LanguageServer.Protocol;
2124
using Microsoft.Python.Parsing.Ast;
2225

2326
namespace Microsoft.Python.LanguageServer.Completion {
24-
internal static class ExpressionCompletion {
27+
internal static class ExpressionCompletion {
2528
public static IEnumerable<CompletionItem> GetCompletionsFromMembers(Expression e, IScope scope, CompletionContext context) {
2629
using (context.Analysis.ExpressionEvaluator.OpenScope(scope)) {
2730
return GetItemsFromExpression(e, context);
@@ -35,14 +38,19 @@ public static IEnumerable<CompletionItem> GetCompletionsFromMembers(Expression e
3538
}
3639

3740
private static IEnumerable<CompletionItem> GetItemsFromExpression(Expression e, CompletionContext context) {
38-
var value = context.Analysis.ExpressionEvaluator.GetValueFromExpression(e);
41+
var eval = context.Analysis.ExpressionEvaluator;
42+
var value = eval.GetValueFromExpression(e);
3943
if (!value.IsUnknown()) {
40-
var items = new List<CompletionItem>();
44+
4145
var type = value.GetPythonType();
42-
var names = type.GetMemberNames().ToArray();
43-
foreach (var t in names) {
46+
if(type is IPythonClassType cls) {
47+
return GetClassItems(cls, e, context);
48+
}
49+
50+
var items = new List<CompletionItem>();
51+
foreach (var t in type.GetMemberNames().ToArray()) {
4452
var m = type.GetMember(t);
45-
if(m is IVariable v && v.Source != VariableSource.Declaration) {
53+
if (m is IVariable v && v.Source != VariableSource.Declaration) {
4654
continue;
4755
}
4856
items.Add(context.ItemSource.CreateCompletionItem(t, m, type));
@@ -51,5 +59,36 @@ private static IEnumerable<CompletionItem> GetItemsFromExpression(Expression e,
5159
}
5260
return Enumerable.Empty<CompletionItem>();
5361
}
62+
63+
private static IEnumerable<CompletionItem> GetClassItems(IPythonClassType cls, Expression e, CompletionContext context) {
64+
var eval = context.Analysis.ExpressionEvaluator;
65+
// See if we are completing on self. Note that we may be inside inner function
66+
// that does not necessarily have 'self' argument so we are looking beyond local
67+
// scope. We then check that variable type matches the class type, if any.
68+
var selfVariable = eval.LookupNameInScopes("self");
69+
var completingOnSelf = cls.Equals(selfVariable?.GetPythonType()) && e is NameExpression nex && nex.Name == "self";
70+
71+
var items = new List<CompletionItem>();
72+
var names = cls.GetMemberNames().ToArray();
73+
74+
foreach (var t in names) {
75+
var m = cls.GetMember(t);
76+
if (m is IVariable v && v.Source != VariableSource.Declaration) {
77+
continue;
78+
}
79+
// If this is class member completion, unmangle private member names.
80+
var unmangledName = cls.UnmangleMemberName(t);
81+
if (!string.IsNullOrEmpty(unmangledName)) {
82+
// Hide private variables outside of the class scope.
83+
if (!completingOnSelf && cls.IsPrivateMember(t)) {
84+
continue;
85+
}
86+
items.Add(context.ItemSource.CreateCompletionItem(unmangledName, m, cls));
87+
} else {
88+
items.Add(context.ItemSource.CreateCompletionItem(t, m, cls));
89+
}
90+
}
91+
return items;
92+
}
5493
}
5594
}

src/LanguageServer/Test/CompletionTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,5 +1150,27 @@ def func():
11501150
var result = cs.GetCompletions(analysis, new SourceLocation(4, 2));
11511151
result.Completions.Select(c => c.label).Should().NotContain("aaa");
11521152
}
1153+
1154+
[TestMethod, Priority(0)]
1155+
public async Task PrivateMembers() {
1156+
const string code = @"
1157+
class A:
1158+
def __init__(self):
1159+
self.__x = 123
1160+
1161+
def func(self):
1162+
self.
1163+
1164+
A().
1165+
";
1166+
var analysis = await GetAnalysisAsync(code);
1167+
var cs = new CompletionSource(new PlainTextDocumentationSource(), ServerSettings.completion);
1168+
1169+
var result = cs.GetCompletions(analysis, new SourceLocation(7, 14));
1170+
result.Completions.Select(c => c.label).Should().Contain("__x").And.NotContain("_A__x");
1171+
1172+
result = cs.GetCompletions(analysis, new SourceLocation(9, 5));
1173+
result.Completions.Select(c => c.label).Should().NotContain("_A__x").And.NotContain("__x");
1174+
}
11531175
}
11541176
}

0 commit comments

Comments
 (0)