diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart index 6e6309a75b9c..152ba37e8f78 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart @@ -126,7 +126,7 @@ class TextDocumentOpenHandler ); server.onOverlayCreated(path, doc.text); - final driver = server.contextManager.getDriverFor(path); + final driver = server.getAnalysisDriver(path); // If the file did not exist, and is "overlay only", it still should be // analyzed. Add it to driver to which it should have been added. diff --git a/pkg/analysis_server/test/lsp/change_workspace_folders_test.dart b/pkg/analysis_server/test/lsp/change_workspace_folders_test.dart index 3b42924858de..95e11f479954 100644 --- a/pkg/analysis_server/test/lsp/change_workspace_folders_test.dart +++ b/pkg/analysis_server/test/lsp/change_workspace_folders_test.dart @@ -192,6 +192,43 @@ class ChangeWorkspaceFoldersTest extends AbstractLspAnalysisServerTest { ); } + Future test_changeWorkspaceFolders_openFileOutsideRoot() async { + // When a file is opened that is outside of the analysis roots, the first + // analysis driver will be used (see [AbstractAnalysisServer.getAnalysisDriver]). + // This means as long as there is already an analysis root, the implicit root + // will be the original root and not the path of the opened file. + // For example, Go-to-Definition into a file in PubCache must *not* result in + // the pub cache folder being added as an analysis root, it should be analyzed + // by the existing project's driver. + final workspace1FilePath = join(workspaceFolder1Path, 'test.dart'); + await newFile(workspace1FilePath); + final workspace2FilePath = join(workspaceFolder2Path, 'test.dart'); + final workspace2FileUri = Uri.file(workspace2FilePath); + await newFile(workspace2FilePath); + + await initialize(workspaceFolders: [workspaceFolder1Uri]); + + // Expect explicit root for the workspace folder. + expect( + server.contextManager.includedPaths, + unorderedEquals([workspaceFolder1Path]), + ); + + // Open a file in workspaceFolder2 (which is not in the analysis roots). + await openFile(workspace2FileUri, ''); + expect( + server.contextManager.includedPaths, + unorderedEquals([workspaceFolder1Path]), + ); + + // Closing the file should not result in the project being removed. + await closeFile(workspace2FileUri); + expect( + server.contextManager.includedPaths, + unorderedEquals([workspaceFolder1Path]), + ); + } + Future test_changeWorkspaceFolders_remove() async { await initialize( workspaceFolders: [workspaceFolder1Uri, workspaceFolder2Uri], diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart index 2827fd4a6626..ceb5d63a4657 100644 --- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart +++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart @@ -983,10 +983,11 @@ class KernelTypeGraphBuilder extends ir.Visitor { if (variable != null) { Local local = _localsMap.getLocalVariable(variable); if (!_capturedVariables.contains(local)) { + // Receiver strengthening to non-null. DartType type = _localsMap.getLocalType(_elementMap, local); _state.updateLocal( _inferrer, _capturedAndBoxed, local, receiverType, node, type, - isNullable: selector.appliesToNullWithoutThrow()); + excludeNull: !selector.appliesToNullWithoutThrow()); } } @@ -1445,11 +1446,16 @@ class KernelTypeGraphBuilder extends ir.Visitor { ir.Expression operand = node.operand; if (operand is ir.VariableGet) { Local local = _localsMap.getLocalVariable(operand.variable); - DartType type = _elementMap.getDartType(node.type); + DartType localType = _elementMap.getDartType(node.type); LocalState stateAfterCheckWhenTrue = new LocalState.childPath(_state); LocalState stateAfterCheckWhenFalse = new LocalState.childPath(_state); - stateAfterCheckWhenTrue.narrowLocal( - _inferrer, _capturedAndBoxed, local, type, node); + + // Narrow variable to tested type on true branch. + TypeInformation currentTypeInformation = stateAfterCheckWhenTrue + .readLocal(_inferrer, _capturedAndBoxed, local); + stateAfterCheckWhenTrue.updateLocal(_inferrer, _capturedAndBoxed, local, + currentTypeInformation, node, localType, + isCast: false); _setStateAfter(_state, stateAfterCheckWhenTrue, stateAfterCheckWhenFalse); } } @@ -1462,10 +1468,18 @@ class KernelTypeGraphBuilder extends ir.Visitor { DartType localType = _localsMap.getLocalType(_elementMap, local); LocalState stateAfterCheckWhenTrue = new LocalState.childPath(_state); LocalState stateAfterCheckWhenFalse = new LocalState.childPath(_state); + + // Narrow tested variable to 'Null' on true branch. stateAfterCheckWhenTrue.updateLocal(_inferrer, _capturedAndBoxed, local, _types.nullType, node, localType); - stateAfterCheckWhenFalse.narrowLocal(_inferrer, _capturedAndBoxed, local, - _closedWorld.commonElements.objectType, node); + + // Narrow tested variable to 'not null' on false branch. + TypeInformation currentTypeInformation = stateAfterCheckWhenFalse + .readLocal(_inferrer, _capturedAndBoxed, local); + stateAfterCheckWhenFalse.updateLocal(_inferrer, _capturedAndBoxed, local, + currentTypeInformation, node, _closedWorld.commonElements.objectType, + excludeNull: true); + _setStateAfter(_state, stateAfterCheckWhenTrue, stateAfterCheckWhenFalse); } } @@ -1620,7 +1634,7 @@ class KernelTypeGraphBuilder extends ir.Visitor { DartType type = _localsMap.getLocalType(_elementMap, local); _state.updateLocal( _inferrer, _capturedAndBoxed, local, localFunctionType, node, type, - isNullable: false); + excludeNull: true); } // We don't put the closure in the work queue of the @@ -1737,7 +1751,7 @@ class KernelTypeGraphBuilder extends ir.Visitor { Local local = _localsMap.getLocalVariable(exception); _state.updateLocal(_inferrer, _capturedAndBoxed, local, mask, node, _dartTypes.dynamicType(), - isNullable: false /* `throw null` produces a NullThrownError */); + excludeNull: true /* `throw null` produces a NullThrownError */); } ir.VariableDeclaration stackTrace = node.stackTrace; if (stackTrace != null) { @@ -2088,9 +2102,11 @@ class LocalState { TypeInformation type, ir.Node node, DartType staticType, - {isNullable: true}) { + {isCast: true, + excludeNull: false}) { assert(type != null); - type = inferrer.types.narrowType(type, staticType, isNullable: isNullable); + type = inferrer.types + .narrowType(type, staticType, isCast: isCast, excludeNull: excludeNull); FieldEntity field = capturedAndBoxed[local]; if (field != null) { @@ -2100,17 +2116,6 @@ class LocalState { } } - void narrowLocal( - InferrerEngine inferrer, - Map capturedAndBoxed, - Local local, - DartType type, - ir.Node node) { - TypeInformation currentType = readLocal(inferrer, capturedAndBoxed, local); - updateLocal(inferrer, capturedAndBoxed, local, currentType, node, type, - isNullable: false); - } - LocalState mergeFlow(InferrerEngine inferrer, LocalState other) { seenReturnOrThrow = false; seenBreakOrContinue = false; diff --git a/pkg/compiler/lib/src/inferrer/type_system.dart b/pkg/compiler/lib/src/inferrer/type_system.dart index 865fbcc31011..3817dc9cd4bd 100644 --- a/pkg/compiler/lib/src/inferrer/type_system.dart +++ b/pkg/compiler/lib/src/inferrer/type_system.dart @@ -7,6 +7,7 @@ import '../common.dart'; import '../constants/values.dart' show BoolConstantValue; import '../elements/entities.dart'; import '../elements/types.dart'; +import '../ir/static_type.dart' show ClassRelation; import '../world.dart'; import 'abstract_value_domain.dart'; import 'type_graph_nodes.dart'; @@ -332,46 +333,42 @@ class TypeSystem { _abstractValueDomain.isNull(type.typeAnnotation).isDefinitelyFalse; /// Returns the intersection between [type] and [annotation]. - /// [isNullable] indicates whether the annotation implies a null - /// type. + /// + /// [isCast] indicates whether narrowing comes from a cast or parameter check + /// rather than an 'is' test. (In legacy semantics these differ on whether + /// `null` is accepted). + /// + /// If [excludeNull] is true, the intersection excludes `null` even if the + /// Dart type implies `null`. TypeInformation narrowType(TypeInformation type, DartType annotation, - {bool isNullable: true}) { - TypeInformation _narrowTo(AbstractValue otherType) { - if (_abstractValueDomain.isExact(type.type).isDefinitelyTrue) return type; - if (isNullable) { - otherType = _abstractValueDomain.includeNull(otherType); - } - TypeInformation newType = - new NarrowTypeInformation(_abstractValueDomain, type, otherType); - allocatedTypes.add(newType); - return newType; + {bool isCast: true, bool excludeNull: false}) { + // Avoid refining an input with an exact type. It we are almost always + // adding a narrowing to a subtype of the same class or a superclass. + if (_abstractValueDomain.isExact(type.type).isDefinitelyTrue) return type; + + AbstractValueWithPrecision narrowing = + _abstractValueDomain.createFromStaticType(annotation, + classRelation: ClassRelation.subtype, nullable: isCast); + + AbstractValue abstractValue = narrowing.abstractValue; + if (excludeNull) { + abstractValue = _abstractValueDomain.excludeNull(abstractValue); } - // TODO(fishythefish): Use nullability. - annotation = annotation.withoutNullability; - if (annotation is VoidType) return type; - if (_closedWorld.dartTypes.isTopType(annotation)) { - if (isNullable) return type; + if (_abstractValueDomain.containsAll(abstractValue).isPotentiallyTrue) { + // Top, or non-nullable Top. + if (_abstractValueDomain.isNull(abstractValue).isPotentiallyTrue) { + return type; + } // If the input is already narrowed to be not-null, there is no value // in adding another narrowing node. if (_isNonNullNarrow(type)) return type; - return _narrowTo(_abstractValueDomain.excludeNull(dynamicType.type)); - } else if (annotation is NeverType) { - return _narrowTo(_abstractValueDomain.emptyType); - } else if (annotation is InterfaceType) { - return _narrowTo( - _abstractValueDomain.createNonNullSubtype(annotation.element)); - } else if (annotation is FunctionType) { - return _narrowTo(functionType.type); - } else if (annotation is FutureOrType) { - // TODO(johnniwinther): Support narrowing of FutureOr. - return type; - } else if (annotation is TypeVariableType) { - // TODO(ngeoffray): Narrow to bound. - return type; - } else { - throw 'Unexpected annotation type $annotation'; } + + TypeInformation newType = + NarrowTypeInformation(_abstractValueDomain, type, abstractValue); + allocatedTypes.add(newType); + return newType; } ParameterTypeInformation getInferredTypeOfParameter(Local parameter) { diff --git a/pkg/compiler/test/analyses/api_allowed.json b/pkg/compiler/test/analyses/api_allowed.json index 737224bba41c..e303a89c0cd8 100644 --- a/pkg/compiler/test/analyses/api_allowed.json +++ b/pkg/compiler/test/analyses/api_allowed.json @@ -69,7 +69,6 @@ "Dynamic access of 'dart.collection::_modifications'.": 5, "Dynamic access of 'dart.collection::_map'.": 4, "Dynamic access of 'dart.collection::_elements'.": 1, - "Dynamic access of 'dart.collection::_element'.": 1, "Dynamic access of 'dart.collection::_first'.": 1 }, "org-dartlang-sdk:///lib/html/dart2js/html_dart2js.dart": { @@ -198,4 +197,4 @@ "org-dartlang-sdk:///lib/_http/websocket_impl.dart": { "Dynamic invocation of 'dart._http::_toJSON'.": 1 } -} +} \ No newline at end of file diff --git a/sdk/lib/_internal/js_runtime/lib/collection_patch.dart b/sdk/lib/_internal/js_runtime/lib/collection_patch.dart index a1d76bfe4ff0..4d4076ce480e 100644 --- a/sdk/lib/_internal/js_runtime/lib/collection_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/collection_patch.dart @@ -357,7 +357,7 @@ class _HashMap extends MapBase implements HashMap { JS('void', 'delete #[#]', table, key); } - List _getBucket(var table, var key) { + List? _getBucket(var table, var key) { var hash = _computeHashCode(key); return JS('var', '#[#]', table, hash); } @@ -905,7 +905,7 @@ class _HashSet extends _SetBase implements HashSet { var bucket = _getBucket(rest, object); var index = _findBucketIndex(bucket, object); if (index < 0) return null; - return bucket[index]; + return JS('', '#[#]', bucket, index); } // Collection. @@ -1091,7 +1091,7 @@ class _HashSet extends _SetBase implements HashSet { JS('void', 'delete #[#]', table, key); } - List _getBucket(var table, var element) { + List? _getBucket(var table, var element) { var hash = _computeHashCode(element); return JS('var', '#[#]', table, hash); } @@ -1357,7 +1357,7 @@ class _LinkedHashSet extends _SetBase implements LinkedHashSet { var bucket = _getBucket(rest, object); var index = _findBucketIndex(bucket, object); if (index < 0) return null; - return bucket[index]._element; + return JS<_LinkedHashSetCell>('', '#[#]', bucket, index)._element; } void forEach(void action(E element)) { @@ -1564,7 +1564,7 @@ class _LinkedHashSet extends _SetBase implements LinkedHashSet { JS('void', 'delete #[#]', table, key); } - List _getBucket(var table, var element) { + List? _getBucket(var table, var element) { var hash = _computeHashCode(element); return JS('var', '#[#]', table, hash); } diff --git a/tests/language/regress/regress43366_test.dart b/tests/language/regress/regress43366_test.dart new file mode 100644 index 000000000000..a24d7d450988 --- /dev/null +++ b/tests/language/regress/regress43366_test.dart @@ -0,0 +1,28 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +String upcase(String? s) { + if (s == null) return ''; + return s.toUpperCase(); +} + +String format(dynamic thing) { + if (thing is String?) return upcase(thing); + if (thing is num) return '$thing'; + return '?'; +} + +main() { + log(format(null)); + log(format('hello')); + log(format([])); + + if (trace != '[][HELLO][?]') throw 'Unexpected: "$trace"'; +} + +String trace = ''; + +void log(String s) { + trace += '[$s]'; +} diff --git a/tools/VERSION b/tools/VERSION index 85898c45c9bb..c3da8da17ddc 100644 --- a/tools/VERSION +++ b/tools/VERSION @@ -28,4 +28,4 @@ MAJOR 2 MINOR 10 PATCH 0 PRERELEASE 110 -PRERELEASE_PATCH 1 +PRERELEASE_PATCH 3