diff --git a/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs b/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs index 8f5743c70..285fc2f86 100644 --- a/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs +++ b/src/Analysis/Ast/Impl/Analyzer/AnalysisWalker.cs @@ -69,12 +69,8 @@ public override bool Walk(ExpressionStatement node) { case Comprehension comp: Eval.ProcessComprehension(comp); return false; - case CallExpression callex when callex.Target is NameExpression nex && !string.IsNullOrEmpty(nex.Name): - Eval.LookupNameInScopes(nex.Name)?.AddReference(Eval.GetLocationOfName(nex)); - return true; - case CallExpression callex when callex.Target is MemberExpression mex && !string.IsNullOrEmpty(mex.Name): - var t = Eval.GetValueFromExpression(mex.Target)?.GetPythonType(); - t?.GetMember(mex.Name)?.AddReference(Eval.GetLocationOfName(mex)); + case CallExpression callex: + Eval.ProcessCallForReferences(callex); return true; default: return base.Walk(node); diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs index 098595dde..462e2fee0 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Callables.cs @@ -383,5 +383,28 @@ private void DeclareParameter(Parameter p, ParameterInfo pi) { DeclareVariable(p.Name, paramType.CreateInstance(ArgumentSet.Empty(p.NameExpression, this)), VariableSource.Declaration, p.NameExpression); } + + internal void ProcessCallForReferences(CallExpression callExpr) { + if (Module.ModuleType != ModuleType.User) { + return; + } + + switch (callExpr.Target) { + case NameExpression nex when !string.IsNullOrEmpty(nex.Name): + // Add reference to the function + this.LookupNameInScopes(nex.Name)?.AddReference(GetLocationOfName(nex)); + break; + case MemberExpression mex when !string.IsNullOrEmpty(mex.Name): { + var t = GetValueFromExpression(mex.Target)?.GetPythonType(); + t?.GetMember(mex.Name)?.AddReference(GetLocationOfName(mex)); + break; + } + } + + // Add references to all arguments. + foreach (var arg in callExpr.Args) { + GetValueFromExpression(arg.Expression); + } + } } } diff --git a/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs b/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs index f153cae4b..3f6ec0113 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Handlers/FromImportHandler.cs @@ -99,20 +99,29 @@ private void HandleModuleImportStar(PythonVariableModule variableModule, IImport /// Import search result. /// Name of the variable to declare, such as 'd' in 'from a.b import c as d'. /// Position of the import statement. - /// Name expression of the variable. - private void DeclareVariable(PythonVariableModule variableModule, string memberName, IImportSearchResult imports, string variableName, int importPosition, Node nameExpression) { + /// Location of the variable name expression. + private void DeclareVariable(PythonVariableModule variableModule, string memberName, IImportSearchResult imports, string variableName, int importPosition, Node nameLocation) { // First try imports since child modules should win, i.e. in 'from a.b import c' // 'c' should be a submodule if 'b' has one, even if 'b' also declares 'c = 1'. var value = GetValueFromImports(variableModule, imports as IImportChildrenSource, memberName); + // First try exported or child submodules. value = value ?? variableModule.GetMember(memberName); + // Value may be variable or submodule. If it is variable, we need it in order to add reference. var variable = variableModule.Analysis?.GlobalScope?.Variables[memberName]; value = variable?.Value?.Equals(value) == true ? variable : value; + // If nothing is exported, variables are still accessible. value = value ?? variableModule.Analysis?.GlobalScope?.Variables[memberName]?.Value ?? Eval.UnknownType; + // Do not allow imported variables to override local declarations - Eval.DeclareVariable(variableName, value, VariableSource.Import, nameExpression, CanOverwriteVariable(variableName, importPosition)); + var canOverwrite = CanOverwriteVariable(variableName, importPosition); + + // Do not declare references to '*' + var locationExpression = nameLocation is NameExpression nex && nex.Name == "*" ? null : nameLocation; + Eval.DeclareVariable(variableName, value, VariableSource.Import, locationExpression, canOverwrite); + // Make sure module is loaded and analyzed. if (value is IPythonModule m) { ModuleResolution.GetOrLoadModule(m.Name); diff --git a/src/Analysis/Ast/Impl/Types/LocatedMember.cs b/src/Analysis/Ast/Impl/Types/LocatedMember.cs index 2a70b4004..ca863d95b 100644 --- a/src/Analysis/Ast/Impl/Types/LocatedMember.cs +++ b/src/Analysis/Ast/Impl/Types/LocatedMember.cs @@ -54,7 +54,11 @@ public virtual IReadOnlyList References { public virtual void AddReference(Location location) { lock (this) { - if(this.DeclaringModule == null || this.DeclaringModule?.ModuleType == ModuleType.Builtins) { + // In order to limit memory consumption we normally don't track references + // to builtin types such as int or list. Exception is functions like 'print' + // since it user may want to find all references to them. + if (this.DeclaringModule == null || + (this.DeclaringModule?.ModuleType == ModuleType.Builtins && MemberType != PythonMemberType.Function)) { return; } // Don't add references to library code. diff --git a/src/Analysis/Ast/Impl/Types/PythonType.cs b/src/Analysis/Ast/Impl/Types/PythonType.cs index 4abb24c38..53adbe358 100644 --- a/src/Analysis/Ast/Impl/Types/PythonType.cs +++ b/src/Analysis/Ast/Impl/Types/PythonType.cs @@ -47,14 +47,6 @@ private PythonType(string name, Location location, BuiltinTypeId typeId) : base( #region ILocatedMember public override PythonMemberType MemberType => _typeId.GetMemberId(); - - public override void AddReference(Location location) { - if (DeclaringModule == null || DeclaringModule.ModuleType == ModuleType.Builtins) { - return; - } - - base.AddReference(location); - } #endregion #region IPythonType diff --git a/src/Analysis/Ast/Test/ReferencesTests.cs b/src/Analysis/Ast/Test/ReferencesTests.cs index bf1fa2418..a27a26754 100644 --- a/src/Analysis/Ast/Test/ReferencesTests.cs +++ b/src/Analysis/Ast/Test/ReferencesTests.cs @@ -441,5 +441,59 @@ public async Task ExtendAllAssignment() { all.References[3].Span.Should().Be(12, 1, 12, 8); all.References[4].Span.Should().Be(13, 1, 13, 8); } + + [TestMethod, Priority(0)] + public async Task VariableInCallParameters() { + const string code = @" +from constants import * +import constants + +print(VARIABLE1) +print(constants.VARIABLE1) +x = print(VARIABLE1) +"; + await TestData.CreateTestSpecificFileAsync("constants.py", @"VARIABLE1 = 'afad'"); + var analysis = await GetAnalysisAsync(code); + var v1 = analysis.Should().HaveVariable("VARIABLE1").Which; + + v1.Definition.Span.Should().Be(1, 1, 1, 10); + v1.Definition.DocumentUri.AbsolutePath.Should().Contain("constants.py"); + + v1.References.Should().HaveCount(4); + v1.References[0].Span.Should().Be(1, 1, 1, 10); + v1.References[0].DocumentUri.AbsolutePath.Should().Contain("constants.py"); + + v1.References[1].Span.Should().Be(5, 7, 5, 16); + v1.References[1].DocumentUri.AbsolutePath.Should().Contain("module.py"); + + v1.References[2].Span.Should().Be(6, 17, 6, 26); + v1.References[2].DocumentUri.AbsolutePath.Should().Contain("module.py"); + + v1.References[3].Span.Should().Be(7, 11, 7, 20); + v1.References[3].DocumentUri.AbsolutePath.Should().Contain("module.py"); + } + + [TestMethod, Priority(0)] + public async Task LibraryFunction() { + const string code = @" +print(1) +print(2) +"; + var analysis = await GetAnalysisAsync(code); + var b = analysis.Document.Interpreter.ModuleResolution.BuiltinsModule; + var print = b.Analysis.Should().HaveVariable("print").Which; + + print.Definition.Span.Should().Be(1, 1, 1, 1); + + print.References.Should().HaveCount(3); + print.References[0].Span.Should().Be(1, 1, 1, 1); + print.References[0].DocumentUri.AbsolutePath.Should().Contain("python.pyi"); + + print.References[1].Span.Should().Be(2, 1, 2, 6); + print.References[1].DocumentUri.AbsolutePath.Should().Contain("module.py"); + + print.References[2].Span.Should().Be(3, 1, 3, 6); + print.References[2].DocumentUri.AbsolutePath.Should().Contain("module.py"); + } } } diff --git a/src/Parsing/Impl/Ast/CallExpression.cs b/src/Parsing/Impl/Ast/CallExpression.cs index 9a5231862..00522512f 100644 --- a/src/Parsing/Impl/Ast/CallExpression.cs +++ b/src/Parsing/Impl/Ast/CallExpression.cs @@ -17,7 +17,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Python.Core; using Microsoft.Python.Core.Collections; namespace Microsoft.Python.Parsing.Ast {