diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs index 62823a8a2..78d0ecc8f 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Collections.cs @@ -60,7 +60,7 @@ public IMember GetValueFromList(ListExpression expression) { var value = GetValueFromExpression(item) ?? UnknownType; contents.Add(value); } - return PythonCollectionType.CreateList(Module.Interpreter, GetLoc(expression), contents); + return PythonCollectionType.CreateList(Module.Interpreter, GetLoc(expression), contents, exact: true); } public IMember GetValueFromDictionary(DictionaryExpression expression) { @@ -70,7 +70,7 @@ public IMember GetValueFromDictionary(DictionaryExpression expression) { var value = GetValueFromExpression(item.SliceStop) ?? UnknownType; contents[key] = value; } - return new PythonDictionary(Interpreter, GetLoc(expression), contents); + return new PythonDictionary(Interpreter, GetLoc(expression), contents, exact: true); } private IMember GetValueFromTuple(TupleExpression expression) { @@ -79,7 +79,7 @@ private IMember GetValueFromTuple(TupleExpression expression) { var value = GetValueFromExpression(item) ?? UnknownType; contents.Add(value); } - return PythonCollectionType.CreateTuple(Module.Interpreter, GetLoc(expression), contents); + return PythonCollectionType.CreateTuple(Module.Interpreter, GetLoc(expression), contents, exact: true); } public IMember GetValueFromSet(SetExpression expression) { @@ -88,7 +88,7 @@ public IMember GetValueFromSet(SetExpression expression) { var value = GetValueFromExpression(item) ?? UnknownType; contents.Add(value); } - return PythonCollectionType.CreateSet(Interpreter, GetLoc(expression), contents); + return PythonCollectionType.CreateSet(Interpreter, GetLoc(expression), contents, exact: true); } public IMember GetValueFromGenerator(GeneratorExpression expression) { @@ -103,6 +103,8 @@ public IMember GetValueFromComprehension(Comprehension node) { var oldVariables = CurrentScope.Variables.OfType().ToDictionary(k => k.Name, v => v); try { ProcessComprehension(node); + + // TODO: Evaluate comprehensions to produce exact contents, if possible. switch (node) { case ListComprehension lc: var v1 = GetValueFromExpression(lc.Item) ?? UnknownType; diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs index dd9569905..e0e16e4b2 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Operators.cs @@ -131,7 +131,7 @@ private IMember GetValueFromBinaryOp(Expression expr) { && leftType?.TypeId == BuiltinTypeId.List && rightType?.TypeId == BuiltinTypeId.List && left is IPythonCollection lc && right is IPythonCollection rc) { - return PythonCollectionType.CreateConcatenatedList(Module.Interpreter, GetLoc(expr), lc.Contents, rc.Contents); + return PythonCollectionType.CreateConcatenatedList(Module.Interpreter, GetLoc(expr), lc, rc); } return left.IsUnknown() ? right : left; diff --git a/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs b/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs index 624087c2b..b08a91e4c 100644 --- a/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs +++ b/src/Analysis/Ast/Impl/Analyzer/ModuleWalker.cs @@ -76,14 +76,14 @@ private void HandleAugmentedAllAssign(AugmentedAssignStatement node) { } var rightVar = Eval.GetValueFromExpression(node.Right); - var rightContents = (rightVar as IPythonCollection)?.Contents; + var right = rightVar as IPythonCollection; - if (rightContents == null) { + if (right == null) { _allIsUsable = false; return; } - ExtendAll(node.Left, rightContents); + ExtendAll(node.Left, right); } private void HandleAllAppendExtend(CallExpression node) { @@ -99,31 +99,33 @@ private void HandleAllAppendExtend(CallExpression node) { return; } - IReadOnlyList contents = null; - var v = Eval.GetValueFromExpression(node.Args[0].Expression); + var arg = node.Args[0].Expression; + var v = Eval.GetValueFromExpression(arg); if (v == null) { _allIsUsable = false; return; } + IPythonCollection values = null; + switch (me.Name) { case "append": - contents = new List() { v }; + values = PythonCollectionType.CreateList(Module.Interpreter, Eval.GetLoc(arg), new List() { v }, exact: true); break; case "extend": - contents = (v as IPythonCollection)?.Contents; + values = v as IPythonCollection; break; } - if (contents == null) { + if (values == null) { _allIsUsable = false; return; } - ExtendAll(node, contents); + ExtendAll(node, values); } - private void ExtendAll(Node declNode, IReadOnlyList values) { + private void ExtendAll(Node declNode, IPythonCollection values) { Eval.LookupNameInScopes(AllVariableName, out var scope, LookupOptions.Normal); if (scope == null) { return; @@ -131,9 +133,9 @@ private void ExtendAll(Node declNode, IReadOnlyList values) { var loc = Eval.GetLoc(declNode); - var allContents = (scope.Variables[AllVariableName].Value as IPythonCollection)?.Contents; + var all = scope.Variables[AllVariableName].Value as IPythonCollection; - var list = PythonCollectionType.CreateConcatenatedList(Module.Interpreter, loc, allContents, values); + var list = PythonCollectionType.CreateConcatenatedList(Module.Interpreter, loc, all, values); var source = list.IsGeneric() ? VariableSource.Generic : VariableSource.Declaration; Eval.DeclareVariable(AllVariableName, list, source, loc); @@ -199,7 +201,8 @@ public void Complete() { SymbolTable.ReplacedByStubs.Clear(); MergeStub(); - if (_allIsUsable && _allReferencesCount >= 1 && GlobalScope.Variables.TryGetVariable(AllVariableName, out var variable) && variable?.Value is IPythonCollection collection) { + if (_allIsUsable && _allReferencesCount >= 1 && GlobalScope.Variables.TryGetVariable(AllVariableName, out var variable) + && variable?.Value is IPythonCollection collection && collection.IsExact) { ExportedMemberNames = collection.Contents .OfType() .Select(c => c.GetString()) diff --git a/src/Analysis/Ast/Impl/Types/Collections/PythonCollectionType.cs b/src/Analysis/Ast/Impl/Types/Collections/PythonCollectionType.cs index ac3352e3f..67044546c 100644 --- a/src/Analysis/Ast/Impl/Types/Collections/PythonCollectionType.cs +++ b/src/Analysis/Ast/Impl/Types/Collections/PythonCollectionType.cs @@ -84,34 +84,39 @@ public override IMember Index(IPythonInstance instance, object index) #endregion public static IPythonCollection CreateList(IPythonInterpreter interpreter, LocationInfo location, IArgumentSet args) { - IReadOnlyList contents = null; + var exact = true; + IReadOnlyList contents; + if (args.Arguments.Count > 1) { // self and list like in list.__init__ and 'list([1, 'str', 3.0])' - contents = (args.Arguments[1].Value as PythonCollection)?.Contents; + var arg = args.Arguments[1].Value as PythonCollection; + exact = arg?.IsExact ?? false; + contents = arg?.Contents; } else { - // Try list argument as n '__init__(self, *args, **kwargs)' contents = args.ListArgument?.Values; } - return CreateList(interpreter, location, contents ?? Array.Empty()); + + return CreateList(interpreter, location, contents ?? Array.Empty(), exact: exact); } - public static IPythonCollection CreateList(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyList contents, bool flatten = true) { + public static IPythonCollection CreateList(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyList contents, bool flatten = true, bool exact = false) { var collectionType = new PythonCollectionType(null, BuiltinTypeId.List, interpreter, true); - return new PythonCollection(collectionType, location, contents, flatten); + return new PythonCollection(collectionType, location, contents, flatten, exact); } - public static IPythonCollection CreateConcatenatedList(IPythonInterpreter interpreter, LocationInfo location, params IReadOnlyList[] manyContents) { - var contents = manyContents?.ExcludeDefault().SelectMany().ToList() ?? new List(); - return CreateList(interpreter, location, contents); + public static IPythonCollection CreateConcatenatedList(IPythonInterpreter interpreter, LocationInfo location, params IPythonCollection[] many) { + var exact = many?.All(c => c != null && c.IsExact) ?? false; + var contents = many?.ExcludeDefault().Select(c => c.Contents).SelectMany().ToList() ?? new List(); + return CreateList(interpreter, location, contents, false, exact: exact); } - public static IPythonCollection CreateTuple(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyList contents) { + public static IPythonCollection CreateTuple(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyList contents, bool exact = false) { var collectionType = new PythonCollectionType(null, BuiltinTypeId.Tuple, interpreter, false); - return new PythonCollection(collectionType, location, contents); + return new PythonCollection(collectionType, location, contents, exact: exact); } - public static IPythonCollection CreateSet(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyList contents, bool flatten = true) { + public static IPythonCollection CreateSet(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyList contents, bool flatten = true, bool exact = false) { var collectionType = new PythonCollectionType(null, BuiltinTypeId.Set, interpreter, true); - return new PythonCollection(collectionType, location, contents, flatten); + return new PythonCollection(collectionType, location, contents, flatten, exact: exact); } public override bool Equals(object obj) diff --git a/src/Analysis/Ast/Impl/Values/Collections/PythonCollection.cs b/src/Analysis/Ast/Impl/Values/Collections/PythonCollection.cs index ae80e2c02..bc393fade 100644 --- a/src/Analysis/Ast/Impl/Values/Collections/PythonCollection.cs +++ b/src/Analysis/Ast/Impl/Values/Collections/PythonCollection.cs @@ -25,14 +25,16 @@ internal class PythonCollection : PythonInstance, IPythonCollection { /// Collection type. /// Contents of the collection (typically elements from the initialization). /// Declaring location. - /// If true and contents is a single element + /// If true and contents is a single element + /// True if the contents are an exact representation of the collection contents. /// and is a sequence, the sequence elements are copied rather than creating /// a sequence of sequences with a single element. public PythonCollection( IPythonType collectionType, LocationInfo location, IReadOnlyList contents, - bool flatten = true + bool flatten = true, + bool exact = false ) : base(collectionType, location) { var c = contents ?? Array.Empty(); if (flatten && c.Count == 1 && c[0] is IPythonCollection seq) { @@ -40,6 +42,7 @@ public PythonCollection( } else { Contents = c; } + IsExact = exact; } /// @@ -72,5 +75,7 @@ public static int GetIndex(object index) { return 0; } } + + public bool IsExact { get; } } } diff --git a/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs b/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs index 2357205bc..1077fc5e0 100644 --- a/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs +++ b/src/Analysis/Ast/Impl/Values/Collections/PythonDictionary.cs @@ -28,16 +28,16 @@ internal class PythonDictionary : PythonCollection, IPythonDictionary { private readonly Dictionary _contents = new Dictionary(new KeyComparer()); private readonly IPythonInterpreter _interpreter; - public PythonDictionary(PythonDictionaryType dictType, LocationInfo location, IReadOnlyDictionary contents) : - base(dictType, location, contents.Keys.ToArray()) { + public PythonDictionary(PythonDictionaryType dictType, LocationInfo location, IReadOnlyDictionary contents, bool exact = false) : + base(dictType, location, contents.Keys.ToArray(), exact: exact) { foreach (var kvp in contents) { _contents[kvp.Key] = kvp.Value; } _interpreter = dictType.DeclaringModule.Interpreter; } - public PythonDictionary(IPythonInterpreter interpreter, LocationInfo location, IMember contents) : - base(new PythonDictionaryType(interpreter), location, Array.Empty()) { + public PythonDictionary(IPythonInterpreter interpreter, LocationInfo location, IMember contents, bool exact = false) : + base(new PythonDictionaryType(interpreter), location, Array.Empty(), exact: exact) { if (contents is IPythonDictionary dict) { foreach (var key in dict.Keys) { _contents[key] = dict[key]; @@ -47,8 +47,8 @@ public PythonDictionary(IPythonInterpreter interpreter, LocationInfo location, I _interpreter = interpreter; } - public PythonDictionary(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyDictionary contents) : - this(new PythonDictionaryType(interpreter), location, contents) { + public PythonDictionary(IPythonInterpreter interpreter, LocationInfo location, IReadOnlyDictionary contents, bool exact = false) : + this(new PythonDictionaryType(interpreter), location, contents, exact: exact) { _interpreter = interpreter; } diff --git a/src/Analysis/Ast/Impl/Values/Definitions/IPythonCollection.cs b/src/Analysis/Ast/Impl/Values/Definitions/IPythonCollection.cs index 3d20c8fef..48af43bdb 100644 --- a/src/Analysis/Ast/Impl/Values/Definitions/IPythonCollection.cs +++ b/src/Analysis/Ast/Impl/Values/Definitions/IPythonCollection.cs @@ -25,5 +25,10 @@ public interface IPythonCollection : IPythonInstance { /// Collection contents /// IReadOnlyList Contents { get; } + + /// + /// True if the collection contents contain an accurate representation of the collection's contents. + /// + bool IsExact { get; } } } diff --git a/src/LanguageServer/Test/ImportsTests.cs b/src/LanguageServer/Test/ImportsTests.cs index cfcf130f1..43d7fc622 100644 --- a/src/LanguageServer/Test/ImportsTests.cs +++ b/src/LanguageServer/Test/ImportsTests.cs @@ -586,6 +586,8 @@ from module1 import * [DataRow(@" __all__ = ['A'] __all__.extend(nothing)")] + [DataRow(@" +__all__ = [chr(x + 65) for x in range(1, 2)]")] [DataTestMethod, Priority(0)] public async Task AllUnsupported(string allCode) { var module1Code = @"