diff --git a/CHANGELOG.md b/CHANGELOG.md index 82708ca71b74..6e8e2ae87df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 3.5.0 ### Dart Runtime + - The Dart VM only executes sound null safe code, running of unsound null safe code using the option `--no-sound-null-safety` has been removed. @@ -37,10 +38,22 @@ advantage of these improvements, set your package's ### Libraries +#### `dart:async` + +- Added option for `ParallelWaitError` to get some meta-information that + it can expose in its `toString`, and the `Iterable.wait` and + `(Future,...,Future).wait` extension methods now provide that information. + Should make a `ParallelWaitError` easier to log. + #### `dart:cli` - **Breaking change** [#52121][]: `waitFor` is removed in 3.4. +#### `dart:ffi` + +- Added `Struct.create` and `Union.create` to create struct and union views + of the sequence of bytes stored in a subtype of `TypedData`. + #### `dart:io` - **Breaking change** [#53863][]: `Stdout` has a new field `lineTerminator`, @@ -60,14 +73,47 @@ advantage of these improvements, set your package's to runtime errors when trying to use the return values. The implementation now returns a `JSBoolean` to align with the interface. See issue [#55024] for more details. + - Added `ExternalDartReference` and related conversion functions `toExternalReference` and `toDartObject`. This is a faster alternative to `JSBoxedDartObject`, but with fewer safety guarantees and fewer interoperability capabilities. See [#55187] for more details. +- On dart2wasm, `JSBoxedDartObject` now is an actual JS object that wraps the + opaque Dart value instead of only externalizing the value. Like the JS + backends, you'll now get a more useful error when trying to use it in another + Dart runtime. + +- Added `isA` helper to make type checks easier with interop types. See + [#54138][] for more details. + +[#54138]: https://github.com/dart-lang/sdk/issues/54138 [#55024]: https://github.com/dart-lang/sdk/issues/55024 [#55187]: https://github.com/dart-lang/sdk/issues/55187 +#### `dart:typed_data` + +- **BREAKING CHANGE** [#53218][] [#53785][]: The unmodifiable view classes for + typed data are deprecated. + + To create an unmodifiable view of a typed-data object, use the + `asUnmodifiableView()` methods added in Dart 3.3: + + ```dart + Uint8List data = ...; + final readOnlyView = data.asUnmodifiableView(); + // readOnlyView has type Uint8List, and throws if attempted modified. + ``` + + The reason for this change is to allow more flexibility in the implementation + of typed data, so the native and web platforms can use different strategies + to ensure that typed data has good performance. + + The deprecated types will be removed in Dart 3.5. + +[#53218]: https://github.com/dart-lang/sdk/issues/53218 +[#53785]: https://github.com/dart-lang/sdk/issues/53785 + ### Tools #### Analyzer @@ -75,10 +121,13 @@ advantage of these improvements, set your package's - Improved code completion. Fixed over 50% of completion correctness bugs, tagged `analyzer-completion-correctness` in the [issue tracker][analyzer-completion-correction-issues]. + - Support for new annotations introduced in version 1.14.0 of the [meta] package. + - Support for the [`@doNotSubmit`] annotation, noting that any usage of an annotated member should not be submitted to source control. + - Support for the [`@mustBeConst`] annotation, which indicates that an annotated parameter only accepts constant arguments. @@ -89,16 +138,24 @@ advantage of these improvements, set your package's #### Compilers -- The compilation environment will no longer pretend to contain - entries with value `""` for all `dart.library.foo` strings, - where `dart:foo` is not an available library. - Instead there will only be entries for the available libraries, - like `dart.library.core`, where the value was, and still is, `"true"`. - This should have no effect on `const bool.fromEnvironment(...)` or - `const String.fromEnvironment(...)` without a `defaultValue` - argument, an argument which was always ignored previously. - It changes the behavior of `const bool.hasEnvironment(...)` on such - an input, away from always being `true` and therefore useless. +- The compilation environment will no longer pretend to contain entries with + value `""` for all `dart.library.foo` strings, where `dart:foo` is not an + available library. Instead there will only be entries for the available + libraries, like `dart.library.core`, where the value was, and still is, + `"true"`. This should have no effect on `const bool.fromEnvironment(...)` or + `const String.fromEnvironment(...)` without a `defaultValue` argument, an + argument which was always ignored previously. It changes the behavior of + `const bool.hasEnvironment(...)` on such an input, away from always being + `true` and therefore useless. + +#### DevTools + +- Updated DevTools to version 2.33.0 from 2.31.1. + To learn more, check out the release notes for versions + [2.32.0][devtools-2-32-0] and [2.33.0][devtools-2-33-0]. + +[devtools-2-32-0]: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.32.0 +[devtools-2-33-0]: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.33.0 #### Pub @@ -107,10 +164,14 @@ advantage of these improvements, set your package's `ignored_advisories` section in the `pubspec.yaml` file. To learn more about pub's support for security advisories, visit [dart.dev/go/pub-security-advisories][pub-security-advisories]. -- `path`-dependencies inside `git`-dependencies are now resolved relative to the git - repo. + +- `path`-dependencies inside `git`-dependencies are now resolved relative to the + git repo. + - All `dart pub` commands can now be run from any subdirectory of a project. Pub - will find the first parent directory with a `pubspec.yaml` and operate relative it. + will find the first parent directory with a `pubspec.yaml` and operate + relative it. + - New command `dart pub unpack` that downloads a package from pub.dev and extracts it to a subfolder of the current directory. @@ -118,62 +179,14 @@ advantage of these improvements, set your package's [pub-security-advisories]: https://dart.dev/go/pub-security-advisories -### Libraries - -#### `dart:async` - -- Added option for `ParallelWaitError` to get some meta-information that - it can expose in its `toString`, and the `Iterable.wait` and - `(Future,...,Future).wait` extension methods now provide that information. - Should make a `ParallelWaitError` easier to log. - -#### `dart:ffi` - -- Added `Struct.create` and `Union.create` to create struct and union views - of the sequence of bytes stored in a subtype of `TypedData`. - -#### `dart:js_interop` - -- On dart2wasm, `JSBoxedDartObject` now is an actual JS object that wraps the - opaque Dart value instead of only externalizing the value. Like the JS - backends, you'll now get a more useful error when trying to use it in another - Dart runtime. -- Added `isA` helper to make type checks easier with interop types. See - [#54138][] for more details. - -[#54138]: https://github.com/dart-lang/sdk/issues/54138 - -#### `dart:typed_data` - -- **BREAKING CHANGE** [#53218][] [#53785][]: The unmodifiable view classes for - typed data are deprecated. - - To create an unmodifiable view of a typed-data object, use the - `asUnmodifiableView()` methods added in Dart 3.3: - - ```dart - Uint8List data = ...; - final readOnlyView = data.asUnmodifiableView(); - // readOnlyView has type Uint8List, and throws if attempted modified. - ``` - - The reason for this change is to allow more flexibility in the implementation - of typed data, so the native and web platforms can use different strategies - to ensure that typed data has good performance. - - The deprecated types will be removed in Dart 3.5. - -[#53218]: https://github.com/dart-lang/sdk/issues/53218 -[#53785]: https://github.com/dart-lang/sdk/issues/53785 - ### Dart Runtime -- Dart VM flags and options can now be provided to any executable - generated using `dart compile exe` via the `DART_VM_OPTIONS` environment - variable. `DART_VM_OPTIONS` should be set to a list of comma-separated flags - and options with no whitespace. Options that allow for multiple values to be - provided as comma-separated values are not supported - (e.g., `--timeline-streams=Dart,GC,Compiler`). +- Dart VM flags and options can now be provided to any executable generated + using `dart compile exe` via the `DART_VM_OPTIONS` environment variable. + `DART_VM_OPTIONS` should be set to a list of comma-separated flags and options + with no whitespace. Options that allow for multiple values to be provided as + comma-separated values are not supported (e.g., + `--timeline-streams=Dart,GC,Compiler`). Example of a valid `DART_VM_OPTIONS` environment variable: @@ -185,17 +198,6 @@ advantage of these improvements, set your package's `Dart_NewExternalLatin1String` and `Dart_NewExternalUTF16String` functions are removed from Dart C API. -### Tools - -#### DevTools - -- Updated DevTools to version 2.33.0 from 2.31.1. - To learn more, check out the release notes for versions - [2.32.0][devtools-2-32-0] and [2.33.0][devtools-2-33-0]. - -[devtools-2-32-0]: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.32.0 -[devtools-2-33-0]: https://docs.flutter.dev/tools/devtools/release-notes/release-notes-2.33.0 - ## 3.3.4 - 2024-04-17 This is a patch release that: diff --git a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart index 2dbc327d9980..bd4dfb1bcc0c 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart @@ -2476,6 +2476,19 @@ class InScopeCompletionPass extends SimpleAstVisitor { } } + // `case ^ _:` + // The user want a type for incomplete WildcardPattern. + if (pattern is WildcardPattern) { + if (offset < pattern.name.offset) { + state.request.opType.includeConstructorSuggestions = false; + state.request.opType.mustBeConst = true; + declarationHelper( + mustBeType: true, + ).addLexicalDeclarations(node); + return; + } + } + // `case Name ^:` // The user wants the pattern variable name. if (pattern is ConstantPattern) { @@ -2504,6 +2517,11 @@ class InScopeCompletionPass extends SimpleAstVisitor { state.request.opType.includeConstructorSuggestions = false; type.accept(this); return; + case WildcardPattern(): + collector.completionLocation = 'WildcardPattern_type'; + state.request.opType.includeConstructorSuggestions = false; + type.accept(this); + return; } } @@ -2875,6 +2893,18 @@ class InScopeCompletionPass extends SimpleAstVisitor { } } + @override + void visitWildcardPattern(WildcardPattern node) { + var type = node.type; + if (type != null) { + if (type.coversOffset(offset)) { + declarationHelper( + mustBeType: true, + ).addLexicalDeclarations(node); + } + } + } + @override void visitWithClause(WithClause node) { var parent = node.parent; @@ -3219,10 +3249,10 @@ class InScopeCompletionPass extends SimpleAstVisitor { void _forPattern(AstNode node, {bool mustBeConst = true}) { var coveringNode = state.selection.coveringNode; - // `if (x case ^ y)` - // The user want a type for incomplete DeclaredVariablePattern. if (node case CaseClause caseClause) { var pattern = caseClause.guardedPattern.pattern; + // `if (x case ^ y)` + // The user wants a type for incomplete DeclaredVariablePattern. if (pattern is ConstantPattern) { if (pattern.expression case SimpleIdentifier identifier) { if (!identifier.isSynthetic && offset < identifier.offset) { @@ -3235,6 +3265,18 @@ class InScopeCompletionPass extends SimpleAstVisitor { } } } + // `if (x case ^ _)` + // The user wants a type for incomplete WildcardPattern. + if (pattern is WildcardPattern) { + if (offset < pattern.name.offset) { + state.request.opType.includeConstructorSuggestions = false; + state.request.opType.mustBeConst = true; + declarationHelper( + mustBeType: true, + ).addLexicalDeclarations(node); + return; + } + } } // `if (x case Name^) {}` @@ -3271,6 +3313,11 @@ class InScopeCompletionPass extends SimpleAstVisitor { state.request.opType.includeConstructorSuggestions = false; type.accept(this); return; + case WildcardPattern(): + collector.completionLocation = 'WildcardPattern_type'; + state.request.opType.includeConstructorSuggestions = false; + type.accept(this); + return; } } diff --git a/pkg/analysis_server/lib/src/services/snippets/snippet_producer.dart b/pkg/analysis_server/lib/src/services/snippets/snippet_producer.dart index b6178bf3d61a..ce8cafd30faf 100644 --- a/pkg/analysis_server/lib/src/services/snippets/snippet_producer.dart +++ b/pkg/analysis_server/lib/src/services/snippets/snippet_producer.dart @@ -12,7 +12,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/src/dart/analysis/session_helper.dart'; -import 'package:analyzer/src/lint/linter.dart'; +import 'package:analyzer/src/utilities/extensions/ast.dart'; import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_dart.dart' show DartFileEditBuilderImpl; import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; @@ -49,12 +49,7 @@ abstract class DartSnippetProducer extends SnippetProducer { .getAnalysisOptionsForFile(request.unit.file) .codeStyleOptions; - bool get isInTestDirectory { - var path = request.unit.path; - return LinterContextImpl.getTestDirectories( - request.resourceProvider.pathContext) - .any(path.contains); - } + bool get isInTestDirectory => request.unit.unit.inTestDir; /// Adds public imports for any elements fetched by [getClass] and [getMixin] /// to [builder]. diff --git a/pkg/analysis_server/test/services/completion/dart/location/test_all.dart b/pkg/analysis_server/test/services/completion/dart/location/test_all.dart index 64df883d08cf..51bbb162f19d 100644 --- a/pkg/analysis_server/test/services/completion/dart/location/test_all.dart +++ b/pkg/analysis_server/test/services/completion/dart/location/test_all.dart @@ -77,6 +77,7 @@ import 'try_statement_test.dart' as try_statement; import 'type_argument_list_test.dart' as type_argument_list; import 'type_test_test.dart' as type_test; import 'variable_declaration_list_test.dart' as variable_declaration_list; +import 'wildcard_pattern_test.dart' as wildcard_pattern; import 'with_clause_test.dart' as with_clause; /// Tests suggestions produced at specific locations. @@ -154,6 +155,7 @@ void main() { type_argument_list.main(); type_test.main(); variable_declaration_list.main(); + wildcard_pattern.main(); with_clause.main(); }); } diff --git a/pkg/analysis_server/test/services/completion/dart/location/wildcard_pattern_test.dart b/pkg/analysis_server/test/services/completion/dart/location/wildcard_pattern_test.dart new file mode 100644 index 000000000000..d7fc1c779f45 --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/location/wildcard_pattern_test.dart @@ -0,0 +1,101 @@ +// Copyright (c) 2024, 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. + +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../../../client/completion_driver_test.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(WildcardPatternTest); + }); +} + +@reflectiveTest +class WildcardPatternTest extends AbstractCompletionDriverTest + with WildcardPatternTestCases {} + +mixin WildcardPatternTestCases on AbstractCompletionDriverTest { + void test_ifCase_type() async { + await computeSuggestions(''' +void f(Object? x) { + if (x case ^ _) {} +} + +class A01 {} +class A02 {} +'''); + assertResponse(r''' +suggestions + A01 + kind: class + A02 + kind: class +'''); + } + + void test_ifCase_type_partial() async { + await computeSuggestions(''' +void f(Object? x) { + if (x case A0^ _) {} +} + +class A01 {} +class A02 {} +'''); + assertResponse(r''' +replacement + left: 2 +suggestions + A01 + kind: class + A02 + kind: class +'''); + } + + void test_switchPatternCase_type() async { + await computeSuggestions(''' +void f(Object? x) { + switch (x) { + case ^ _: + break; + } +} + +class A01 {} +class A02 {} +'''); + assertResponse(r''' +suggestions + A01 + kind: class + A02 + kind: class +'''); + } + + void test_switchPatternCase_type_partial() async { + await computeSuggestions(''' +void f(Object? x) { + switch (x) { + case A0^ _: + break; + } +} + +class A01 {} +class A02 {} +'''); + assertResponse(r''' +replacement + left: 2 +suggestions + A01 + kind: class + A02 + kind: class +'''); + } +} diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart index 6585209e33af..4d2e1928b4dc 100644 --- a/pkg/analyzer/lib/src/dart/analysis/driver.dart +++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart @@ -1380,7 +1380,6 @@ class AnalysisDriver { libraryElement, libraryContext.elementFactory.analysisSession.inheritanceManager, library, - resourceProvider.pathContext, testingData: testingData, typeSystemOperations: typeSystemOperations, ).analyze(); @@ -2108,7 +2107,6 @@ class AnalysisDriver { libraryElement, libraryContext.elementFactory.analysisSession.inheritanceManager, library, - resourceProvider.pathContext, testingData: testingData, typeSystemOperations: typeSystemOperations, ).analyzeForCompletion( diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart index 86cf374e3de4..36bded07c833 100644 --- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart +++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart @@ -51,7 +51,6 @@ import 'package:analyzer/src/services/lint.dart'; import 'package:analyzer/src/util/performance/operation_performance.dart'; import 'package:analyzer/src/utilities/extensions/version.dart'; import 'package:analyzer/src/workspace/pub.dart'; -import 'package:path/path.dart' as path; class AnalysisForCompletionResult { final FileState fileState; @@ -71,7 +70,6 @@ class LibraryAnalyzer { final DeclaredVariables _declaredVariables; final LibraryFileKind _library; final InheritanceManager3 _inheritance; - final path.Context _pathContext; final LibraryElementImpl _libraryElement; @@ -82,7 +80,7 @@ class LibraryAnalyzer { final TypeSystemOperations _typeSystemOperations; LibraryAnalyzer(this._analysisOptions, this._declaredVariables, - this._libraryElement, this._inheritance, this._library, this._pathContext, + this._libraryElement, this._inheritance, this._library, {TestingData? testingData, required TypeSystemOperations typeSystemOperations}) : _testingData = testingData, @@ -373,7 +371,6 @@ class LibraryAnalyzer { _inheritance, analysisOptions, unitAnalysis.file.workspacePackage, - _pathContext, ); for (var linter in analysisOptions.lintRules) { linter.reporter = errorReporter; @@ -449,13 +446,10 @@ class LibraryAnalyzer { _typeProvider, _libraryElement, unit, - unitAnalysis.file.content, - declaredVariables: _declaredVariables, typeSystem: _typeSystem, inheritanceManager: _inheritance, analysisOptions: _analysisOptions, workspacePackage: _library.file.workspacePackage, - pathContext: _pathContext, ), ); diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index b0f296921d7d..60b3ff8b3f24 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart @@ -1788,8 +1788,7 @@ final class AugmentedInvocationImpl extends ExpressionImpl @override void resolveExpression(ResolverVisitor resolver, DartType contextType) { - // TODO(scheglov): implement - throw UnimplementedError(); + resolver.visitAugmentedInvocation(this, contextType: contextType); } @override diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart index f4288939a3fe..6ca03abda248 100644 --- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart +++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart @@ -600,7 +600,6 @@ class FileResolver { libraryElement, analysisSession.inheritanceManager, libraryKind, - resourceProvider.pathContext, typeSystemOperations: typeSystemOperations, ); @@ -675,7 +674,6 @@ class FileResolver { libraryElement, libraryContext!.elementFactory.analysisSession.inheritanceManager, libraryKind, - resourceProvider.pathContext, typeSystemOperations: typeSystemOperations, ); diff --git a/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart b/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart index f1abf6786224..69ad25309647 100644 --- a/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart +++ b/pkg/analyzer/lib/src/dart/resolver/invocation_inferrer.dart @@ -98,6 +98,34 @@ class AnnotationInferrer extends FullInvocationInferrer { } } +/// Specialization of [InvocationInferrer] for performing type inference on AST +/// nodes of type [AugmentedInvocation]. +class AugmentedInvocationInferrer + extends FullInvocationInferrer { + AugmentedInvocationInferrer({ + required super.resolver, + required super.node, + required super.argumentList, + required super.contextType, + required super.whyNotPromotedList, + }) : super._(); + + @override + AstNode get _errorNode { + return SimpleIdentifierImpl(node.augmentedKeyword); + } + + @override + bool get _needsTypeArgumentBoundsCheck => true; + + @override + TypeArgumentListImpl? get _typeArguments => node.typeArguments; + + @override + ErrorCode get _wrongNumberOfTypeArgumentsErrorCode => + CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS; +} + /// Specialization of [InvocationInferrer] for performing type inference on AST /// nodes that require full downward and upward inference. abstract class FullInvocationInferrer diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart index b7ecb287a2ee..57a84d420adf 100644 --- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart +++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart @@ -4,7 +4,6 @@ import 'dart:collection'; -import 'package:analyzer/dart/analysis/declared_variables.dart'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/ast/syntactic_entity.dart'; import 'package:analyzer/dart/ast/token.dart'; @@ -33,10 +32,10 @@ import 'package:analyzer/src/error/must_call_super_verifier.dart'; import 'package:analyzer/src/error/null_safe_api_verifier.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/lint/linter.dart'; +import 'package:analyzer/src/utilities/extensions/ast.dart'; import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:analyzer/src/workspace/workspace.dart'; import 'package:meta/meta.dart'; -import 'package:path/path.dart' as path; /// Instances of the class `BestPracticesVerifier` traverse an AST structure /// looking for violations of Dart best practices. @@ -82,9 +81,6 @@ class BestPracticesVerifier extends RecursiveAstVisitor { /// The [WorkspacePackage] in which [_currentLibrary] is declared. final WorkspacePackage? _workspacePackage; - /// The [LinterContext] used for possible const calculations. - final LinterContext _linterContext; - /// True if inference failures should be reported, otherwise false. final bool _strictInference; @@ -96,14 +92,11 @@ class BestPracticesVerifier extends RecursiveAstVisitor { this._errorReporter, TypeProviderImpl typeProvider, this._currentLibrary, - CompilationUnit unit, - String content, { + CompilationUnit unit, { required TypeSystemImpl typeSystem, required InheritanceManager3 inheritanceManager, - required DeclaredVariables declaredVariables, required AnalysisOptions analysisOptions, required WorkspacePackage? workspacePackage, - required path.Context pathContext, }) : _nullType = typeProvider.nullType, _typeSystem = typeSystem, _strictInference = @@ -118,24 +111,12 @@ class BestPracticesVerifier extends RecursiveAstVisitor { _errorReporter, typeProvider, typeSystem, strictCasts: analysisOptions.strictCasts), _invalidAccessVerifier = _InvalidAccessVerifier( - _errorReporter, _currentLibrary, workspacePackage), + _errorReporter, unit, _currentLibrary, workspacePackage), _mustCallSuperVerifier = MustCallSuperVerifier(_errorReporter), _nullSafeApiVerifier = NullSafeApiVerifier(_errorReporter, typeSystem), - _workspacePackage = workspacePackage, - _linterContext = LinterContextImpl( - [], - LinterContextUnit(content, unit), - declaredVariables, - typeProvider, - typeSystem, - inheritanceManager, - analysisOptions, - workspacePackage, - pathContext, - ) { + _workspacePackage = workspacePackage { _deprecatedVerifier.pushInDeprecatedValue(_currentLibrary.hasDeprecated); _inDoNotStoreMember = _currentLibrary.hasDoNotStore; - _invalidAccessVerifier._inTestDirectory = _linterContext.inTestDir(unit); } @override @@ -1607,14 +1588,15 @@ class _InvalidAccessVerifier { final WorkspacePackage? _workspacePackage; final bool _inTemplateSource; - late final bool _inTestDirectory; + final bool _inTestDirectory; InterfaceElement? _enclosingClass; - _InvalidAccessVerifier( - this._errorReporter, this._library, this._workspacePackage) + _InvalidAccessVerifier(this._errorReporter, CompilationUnit unit, + this._library, this._workspacePackage) : _inTemplateSource = - _library.source.fullName.contains(_templateExtension); + _library.source.fullName.contains(_templateExtension), + _inTestDirectory = unit.inTestDir; /// Produces a warning if [identifier] is accessed from an invalid location. /// diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 1d74fc6abb13..7f2b677f1a3f 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -84,6 +84,7 @@ import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:analyzer/src/generated/variable_type_provider.dart'; import 'package:analyzer/src/task/inference_error.dart'; import 'package:analyzer/src/util/ast_data_extractor.dart'; +import 'package:analyzer/src/utilities/extensions/object.dart'; typedef SharedMatchContext = shared.MatchContext; @@ -155,6 +156,9 @@ class ResolverVisitor extends ThrowingAstVisitor /// `null` if the current node is not contained in a function. ExecutableElement? _enclosingFunction; + /// The element that can be referenced by the `augmented` expression. + AugmentableElement? _enclosingAugmentation; + /// The manager for the inheritance mappings. final InheritanceManager3 inheritance; @@ -1350,7 +1354,20 @@ class ResolverVisitor extends ThrowingAstVisitor required Expression node, required bool hasRead, }) { - if (node is IndexExpression) { + if (node is AugmentedExpressionImpl) { + var augmentation = _enclosingAugmentation; + var augmentationTarget = augmentation?.augmentationTarget; + if (augmentation is PropertyAccessorElementImpl && + augmentation.isSetter && + augmentationTarget is PropertyAccessorElementImpl && + augmentationTarget.isSetter) { + node.element = augmentationTarget; + return PropertyElementResolverResult( + writeElementRequested: augmentationTarget, + ); + } + return PropertyElementResolverResult(); + } else if (node is IndexExpression) { var target = node.target; if (target != null) { analyzeExpression(target, UnknownInferredType.instance); @@ -1627,7 +1644,13 @@ class ResolverVisitor extends ThrowingAstVisitor }) { DartType writeType = atDynamicTarget ? DynamicTypeImpl.instance : InvalidTypeImpl.instance; - if (node is IndexExpression) { + if (node is AugmentedExpression) { + if (element is PropertyAccessorElement && element.isSetter) { + if (element.parameters case [var valueParameter]) { + writeType = valueParameter.type; + } + } + } else if (node is IndexExpression) { if (element is MethodElement) { var parameters = element.parameters; if (parameters.length == 2) { @@ -1831,6 +1854,54 @@ class ResolverVisitor extends ThrowingAstVisitor elementResolver.visitAugmentationImportDirective(node); } + @override + void visitAugmentedExpression(covariant AugmentedExpressionImpl node) { + if (_enclosingAugmentation case var augmentation?) { + var augmentationTarget = augmentation.augmentationTarget; + if (augmentation is PropertyAccessorElementImpl && + augmentation.isGetter && + augmentationTarget is PropertyAccessorElementImpl && + augmentationTarget.isGetter) { + node.element = augmentationTarget; + node.staticType = augmentationTarget.returnType; + return; + } + } + + node.staticType = InvalidTypeImpl.instance; + } + + @override + void visitAugmentedInvocation( + covariant AugmentedInvocationImpl node, { + DartType contextType = UnknownInferredType.instance, + }) { + checkUnreachableNode(node); + var whyNotPromotedList = Function()>[]; + + var enclosingAugmentation = _enclosingAugmentation; + var augmentationTarget = enclosingAugmentation?.augmentationTarget; + FunctionType? rawType; + if (augmentationTarget is ExecutableElementImpl) { + node.element = augmentationTarget; + rawType = augmentationTarget.type; + } + + var returnType = AugmentedInvocationInferrer( + resolver: this, + node: node, + argumentList: node.arguments, + contextType: contextType, + whyNotPromotedList: whyNotPromotedList, + ).resolveInvocation(rawType: rawType); + + if (augmentationTarget is ExecutableElementImpl) { + node.staticType = returnType; + } else { + node.staticType = InvalidTypeImpl.instance; + } + } + @override void visitAwaitExpression(AwaitExpression node, {DartType contextType = UnknownInferredType.instance}) { @@ -2040,15 +2111,19 @@ class ResolverVisitor extends ThrowingAstVisitor @override void visitConstructorDeclaration(covariant ConstructorDeclarationImpl node) { + var element = node.declaredElement!; + flowAnalysis.topLevelDeclaration_enter(node, node.parameters); flowAnalysis.executableDeclaration_enter(node, node.parameters, isClosure: false); - var returnType = node.declaredElement!.type.returnType; + var returnType = element.type.returnType; var outerFunction = _enclosingFunction; + var outerAugmentation = _enclosingAugmentation; try { - _enclosingFunction = node.declaredElement; + _enclosingFunction = element; + _enclosingAugmentation = element.ifTypeOrNull(); assert(_thisType == null); _setupThisType(); checkUnreachableNode(node); @@ -2062,6 +2137,7 @@ class ResolverVisitor extends ThrowingAstVisitor elementResolver.visitConstructorDeclaration(node); } finally { _enclosingFunction = outerFunction; + _enclosingAugmentation = outerAugmentation; _thisType = null; } @@ -2474,6 +2550,7 @@ class ResolverVisitor extends ThrowingAstVisitor @override void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) { bool isLocal = node.parent is FunctionDeclarationStatement; + var element = node.declaredElement!; if (isLocal) { flowAnalysis.flow!.functionExpression_begin(node); @@ -2490,8 +2567,12 @@ class ResolverVisitor extends ThrowingAstVisitor var functionType = node.declaredElement!.type; var outerFunction = _enclosingFunction; + var outerAugmentation = _enclosingAugmentation; try { - _enclosingFunction = node.declaredElement; + _enclosingFunction = element; + if (!isLocal) { + _enclosingAugmentation = element.ifTypeOrNull(); + } checkUnreachableNode(node); node.documentationComment?.accept(this); node.metadata.accept(this); @@ -2501,6 +2582,7 @@ class ResolverVisitor extends ThrowingAstVisitor elementResolver.visitFunctionDeclaration(node); } finally { _enclosingFunction = outerFunction; + _enclosingAugmentation = outerAugmentation; } if (!node.isSetter) { @@ -2819,15 +2901,19 @@ class ResolverVisitor extends ThrowingAstVisitor @override void visitMethodDeclaration(covariant MethodDeclarationImpl node) { + var element = node.declaredElement!; + flowAnalysis.topLevelDeclaration_enter(node, node.parameters); flowAnalysis.executableDeclaration_enter(node, node.parameters, isClosure: false); - DartType returnType = node.declaredElement!.returnType; + DartType returnType = element.returnType; var outerFunction = _enclosingFunction; + var outerAugmentation = _enclosingAugmentation; try { - _enclosingFunction = node.declaredElement; + _enclosingFunction = element; + _enclosingAugmentation = element.ifTypeOrNull(); assert(_thisType == null); _setupThisType(); checkUnreachableNode(node); @@ -2840,6 +2926,7 @@ class ResolverVisitor extends ThrowingAstVisitor elementResolver.visitMethodDeclaration(node); } finally { _enclosingFunction = outerFunction; + _enclosingAugmentation = outerAugmentation; _thisType = null; } diff --git a/pkg/analyzer/lib/src/lint/linter.dart b/pkg/analyzer/lib/src/lint/linter.dart index 2d5f74f96cb0..680f3f0cb856 100644 --- a/pkg/analyzer/lib/src/lint/linter.dart +++ b/pkg/analyzer/lib/src/lint/linter.dart @@ -41,6 +41,7 @@ import 'package:analyzer/src/lint/pub.dart'; import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer/src/lint/state.dart'; import 'package:analyzer/src/services/lint.dart' show Linter; +import 'package:analyzer/src/utilities/extensions/ast.dart'; import 'package:analyzer/src/workspace/workspace.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; @@ -232,6 +233,7 @@ abstract class LinterContext { bool canBeConstConstructor(ConstructorDeclaration node); /// Returns `true` if the given [unit] is in a test directory. + @Deprecated('Use `CompilationUnitExtension.inTestDir`.') bool inTestDir(CompilationUnit unit); /// Returns `true` if the [feature] is enabled in the library being linted. @@ -277,8 +279,6 @@ class LinterContextImpl implements LinterContext { @override final InheritanceManager3 inheritanceManager; - final List _testDirectories; - LinterContextImpl( this.allUnits, this.currentUnit, @@ -288,10 +288,8 @@ class LinterContextImpl implements LinterContext { this.inheritanceManager, AnalysisOptions analysisOptions, this.package, - p.Context pathContext, ) : _declaredVariables = declaredVariables, - _analysisOptions = analysisOptions, - _testDirectories = getTestDirectories(pathContext); + _analysisOptions = analysisOptions; @override @Deprecated('This field is being removed; for access to the analysis options ' @@ -313,10 +311,7 @@ class LinterContextImpl implements LinterContext { node.canBeConst; @override - bool inTestDir(CompilationUnit unit) { - var path = unit.declaredElement?.source.fullName; - return path != null && _testDirectories.any(path.contains); - } + bool inTestDir(CompilationUnit unit) => unit.inTestDir; @override bool isEnabled(Feature feature) { @@ -366,16 +361,6 @@ class LinterContextImpl implements LinterContext { return const LinterNameInScopeResolutionResult._none(); } - - static List getTestDirectories(p.Context pathContext) { - var separator = pathContext.separator; - return [ - '${separator}test$separator', - '${separator}integration_test$separator', - '${separator}test_driver$separator', - '${separator}testing$separator', - ]; - } } class LinterContextParsedImpl implements LinterContext { diff --git a/pkg/analyzer/lib/src/test_utilities/find_node.dart b/pkg/analyzer/lib/src/test_utilities/find_node.dart index 4e9087cd11c4..be2e4a85e3b9 100644 --- a/pkg/analyzer/lib/src/test_utilities/find_node.dart +++ b/pkg/analyzer/lib/src/test_utilities/find_node.dart @@ -165,6 +165,8 @@ class FindNode { RethrowExpression get singleRethrowExpression => _single(); + ReturnStatement get singleReturnStatement => _single(); + SetOrMapLiteral get singleSetOrMapLiteral => _single(); SuperConstructorInvocation get singleSuperConstructorInvocation => _single(); diff --git a/pkg/analyzer/lib/src/utilities/extensions/ast.dart b/pkg/analyzer/lib/src/utilities/extensions/ast.dart index 9210273e87d5..84651dde5a99 100644 --- a/pkg/analyzer/lib/src/utilities/extensions/ast.dart +++ b/pkg/analyzer/lib/src/utilities/extensions/ast.dart @@ -115,3 +115,28 @@ extension AstNodeNullableExtension on AstNode? { }; } } + +extension CompilationUnitExtension on CompilationUnit { + /// Whether this [CompilationUnit] is found in a "test" directory. + bool get inTestDir { + final declaredElement = this.declaredElement; + if (declaredElement == null) return false; + var pathContext = declaredElement.session.resourceProvider.pathContext; + var path = declaredElement.source.fullName; + return switch (pathContext.separator) { + '/' => const [ + '/test/', + '/integration_test/', + '/test_driver/', + '/testing/', + ].any(path.contains), + r'\' => const [ + r'\test\', + r'\integration_test\', + r'\test_driver\', + r'\testing\', + ].any(path.contains), + _ => false, + }; + } +} diff --git a/pkg/analyzer/test/src/dart/resolution/augmented_expression_test.dart b/pkg/analyzer/test/src/dart/resolution/augmented_expression_test.dart new file mode 100644 index 000000000000..12cebe312f83 --- /dev/null +++ b/pkg/analyzer/test/src/dart/resolution/augmented_expression_test.dart @@ -0,0 +1,162 @@ +// Copyright (c) 2024, 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. + +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'context_collection_resolution.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(AugmentedExpressionResolutionTest); + }); +} + +@reflectiveTest +class AugmentedExpressionResolutionTest extends PubPackageResolutionTest { + test_class_getter() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +class A { + int get foo => 0; +} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment class A { + augment int get foo { + return augmented; + } +} +'''); + + var node = findNode.singleReturnStatement; + assertResolvedNodeText(node, r''' +ReturnStatement + returnKeyword: return + expression: AugmentedExpression + augmentedKeyword: augmented + element: self::@class::A::@getter::foo + staticType: int + semicolon: ; +'''); + } + + test_class_setter() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +class A { + set foo(int _) {} +} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment class A { + augment set foo(int _) { + augmented = 0; + } +} +'''); + + var node = findNode.singleBlock; + assertResolvedNodeText(node, r''' +Block + leftBracket: { + statements + ExpressionStatement + expression: AssignmentExpression + leftHandSide: AugmentedExpression + augmentedKeyword: augmented + element: self::@class::A::@setter::foo + staticType: null + operator: = + rightHandSide: IntegerLiteral + literal: 0 + parameter: self::@class::A::@setter::foo::@parameter::_ + staticType: int + readElement: + readType: null + writeElement: self::@class::A::@setter::foo + writeType: int + staticElement: + staticType: int + semicolon: ; + rightBracket: } +'''); + } + + test_topLevel_getter() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +int get foo => 0; +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment int get foo { + return augmented; +} +'''); + + var node = findNode.singleReturnStatement; + assertResolvedNodeText(node, r''' +ReturnStatement + returnKeyword: return + expression: AugmentedExpression + augmentedKeyword: augmented + element: self::@getter::foo + staticType: int + semicolon: ; +'''); + } + + test_topLevel_setter() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +set foo(int _) {} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment set foo(int _) { + augmented = 0; +} +'''); + + var node = findNode.singleBlock; + assertResolvedNodeText(node, r''' +Block + leftBracket: { + statements + ExpressionStatement + expression: AssignmentExpression + leftHandSide: AugmentedExpression + augmentedKeyword: augmented + element: self::@setter::foo + staticType: null + operator: = + rightHandSide: IntegerLiteral + literal: 0 + parameter: self::@setter::foo::@parameter::_ + staticType: int + readElement: + readType: null + writeElement: self::@setter::foo + writeType: int + staticElement: + staticType: int + semicolon: ; + rightBracket: } +'''); + } +} diff --git a/pkg/analyzer/test/src/dart/resolution/augmented_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/augmented_invocation_test.dart new file mode 100644 index 000000000000..842950a21c7b --- /dev/null +++ b/pkg/analyzer/test/src/dart/resolution/augmented_invocation_test.dart @@ -0,0 +1,453 @@ +// Copyright (c) 2024, 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. + +import 'package:analyzer/src/error/codes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import 'context_collection_resolution.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(AugmentedInvocationResolutionTest); + }); +} + +@reflectiveTest +class AugmentedInvocationResolutionTest extends PubPackageResolutionTest { + test_class_constructor_named() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +class A { + A.named(int a); +} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment class A { + augment A.named(int a) { + augmented(0); + } +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: self::@class::A::@constructor::named::@parameter::a + staticType: int + rightParenthesis: ) + element: self::@class::A::@constructor::named + staticType: A +'''); + } + + test_class_constructor_unnamed() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +class A { + A(int a); +} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment class A { + augment A(int a) { + augmented(0); + } +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: self::@class::A::@constructor::new::@parameter::a + staticType: int + rightParenthesis: ) + element: self::@class::A::@constructor::new + staticType: A +'''); + } + + test_class_method() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +class A { + void foo(int a) {} +} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment class A { + augment void foo(int a) { + augmented(0); + } +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: self::@class::A::@method::foo::@parameter::a + staticType: int + rightParenthesis: ) + element: self::@class::A::@method::foo + staticType: void +'''); + } + + test_topLevel_function() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +void foo(int a) {} +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment void foo(int a) { + augmented(0); +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: self::@function::foo::@parameter::a + staticType: int + rightParenthesis: ) + element: self::@function::foo + staticType: void +'''); + } + + test_topLevel_function_augments_class() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +class foo {} +'''); + + await assertErrorsInCode(''' +augment library 'a.dart'; + +augment void foo() { + augmented(0); +} +''', [ + error(CompileTimeErrorCode.AUGMENTATION_OF_DIFFERENT_DECLARATION_KIND, 27, + 7), + ]); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: + staticType: int + rightParenthesis: ) + element: + staticType: InvalidType +'''); + } + + test_topLevel_function_generic_fromArgument() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +T foo(T a) => a; +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment void foo(T2 a) { + augmented(0); +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: ParameterMember + base: self::@function::foo::@parameter::a + substitution: {T: int} + staticType: int + rightParenthesis: ) + element: self::@function::foo + staticType: int +'''); + } + + test_topLevel_function_generic_fromArguments_couldNotInfer() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +T foo(T a) => throw 0; +'''); + + await assertErrorsInCode(''' +augment library 'a.dart'; + +augment void foo(T2 a) { + augmented(''); +} +''', [ + error(CompileTimeErrorCode.COULD_NOT_INFER, 70, 9), + ]); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + SimpleStringLiteral + literal: '' + rightParenthesis: ) + element: self::@function::foo + staticType: String +'''); + } + + test_topLevel_function_generic_fromClosure() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +U foo(T t, U Function(T) f) => throw 0; +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment U2 foo(T2 t, U2 Function(T2) f) { + augmented(0, (_) => ''); + throw 0; +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + arguments + IntegerLiteral + literal: 0 + parameter: ParameterMember + base: self::@function::foo::@parameter::t + substitution: {T: int, U: String} + staticType: int + FunctionExpression + parameters: FormalParameterList + leftParenthesis: ( + parameter: SimpleFormalParameter + name: _ + declaredElement: @92::@parameter::_ + type: int + rightParenthesis: ) + body: ExpressionFunctionBody + functionDefinition: => + expression: SimpleStringLiteral + literal: '' + declaredElement: @92 + type: String Function(int) + parameter: ParameterMember + base: self::@function::foo::@parameter::f + substitution: {T: int, U: String} + staticType: String Function(int) + rightParenthesis: ) + element: self::@function::foo + staticType: String +'''); + } + + test_topLevel_function_generic_fromContextType() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +T foo() => throw 0; +'''); + + await assertErrorsInCode(''' +augment library 'a.dart'; + +augment void foo() { + int a = augmented(); +} +''', [ + error(WarningCode.UNUSED_LOCAL_VARIABLE, 58, 1), + ]); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + arguments: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: self::@function::foo + staticType: int +'''); + } + + test_topLevel_function_generic_typeArguments() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +T foo() => throw 0; +'''); + + await assertNoErrorsInCode(''' +augment library 'a.dart'; + +augment void foo() { + augmented(); +} +'''); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + typeArguments: TypeArgumentList + leftBracket: < + arguments + NamedType + name: int + element: dart:core::@class::int + type: int + rightBracket: > + arguments: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: self::@function::foo + staticType: int +'''); + } + + test_topLevel_function_generic_typeArguments_notMatchingBounds() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +T foo() => throw 0; +'''); + + await assertErrorsInCode(''' +augment library 'a.dart'; + +augment void foo() { + augmented(); +} +''', [ + error(CompileTimeErrorCode.TYPE_ARGUMENT_NOT_MATCHING_BOUNDS, 76, 6), + ]); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + typeArguments: TypeArgumentList + leftBracket: < + arguments + NamedType + name: String + element: dart:core::@class::String + type: String + rightBracket: > + arguments: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: self::@function::foo + staticType: String +'''); + } + + test_topLevel_function_generic_typeArguments_wrongNumber() async { + newFile('$testPackageLibPath/a.dart', r''' +import augment 'test.dart'; + +T foo() => throw 0; +'''); + + await assertErrorsInCode(''' +augment library 'a.dart'; + +augment void foo() { + augmented(); +} +''', [ + error(CompileTimeErrorCode.WRONG_NUMBER_OF_TYPE_ARGUMENTS, 63, 13), + ]); + + var node = findNode.singleAugmentedInvocation; + assertResolvedNodeText(node, r''' +AugmentedInvocation + augmentedKeyword: augmented + typeArguments: TypeArgumentList + leftBracket: < + arguments + NamedType + name: int + element: dart:core::@class::int + type: int + NamedType + name: String + element: dart:core::@class::String + type: String + rightBracket: > + arguments: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + element: self::@function::foo + staticType: dynamic +'''); + } +} diff --git a/pkg/analyzer/test/src/dart/resolution/test_all.dart b/pkg/analyzer/test/src/dart/resolution/test_all.dart index b3ba52072019..58ca2a6f8ffe 100644 --- a/pkg/analyzer/test/src/dart/resolution/test_all.dart +++ b/pkg/analyzer/test/src/dart/resolution/test_all.dart @@ -9,6 +9,8 @@ import 'as_expression_test.dart' as as_expression; import 'assignment_test.dart' as assignment; import 'ast_rewrite_test.dart' as ast_rewrite; import 'augmentation_import_test.dart' as augmentation_import; +import 'augmented_expression_test.dart' as augmented_expression; +import 'augmented_invocation_test.dart' as augmented_invocation; import 'await_expression_test.dart' as await_expression; import 'binary_expression_test.dart' as binary_expression; import 'cast_pattern_test.dart' as cast_pattern; @@ -124,6 +126,8 @@ main() { assignment.main(); ast_rewrite.main(); augmentation_import.main(); + augmented_expression.main(); + augmented_invocation.main(); await_expression.main(); binary_expression.main(); cast_pattern.main(); diff --git a/pkg/analyzer/test/src/lint/linter/linter_context_impl_test.dart b/pkg/analyzer/test/src/lint/linter/linter_context_impl_test.dart index 67cd97f722c8..ae8ad9b423c9 100644 --- a/pkg/analyzer/test/src/lint/linter/linter_context_impl_test.dart +++ b/pkg/analyzer/test/src/lint/linter/linter_context_impl_test.dart @@ -46,7 +46,6 @@ abstract class AbstractLinterContextTest extends PubPackageResolutionTest { analysisOptions, // TODO(pq): test package or consider passing in null workspacePackage, - resourceProvider.pathContext, ); } } diff --git a/pkg/dart_internal/CHANGELOG.md b/pkg/dart_internal/CHANGELOG.md index 784576e5783e..b805c2bf4989 100644 --- a/pkg/dart_internal/CHANGELOG.md +++ b/pkg/dart_internal/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.11 + +- Support the latest Dart SDK. + ## 0.2.10 - Support the latest Dart SDK. diff --git a/pkg/dart_internal/pubspec.yaml b/pkg/dart_internal/pubspec.yaml index 91b2d05102ba..c1251bd245bd 100644 --- a/pkg/dart_internal/pubspec.yaml +++ b/pkg/dart_internal/pubspec.yaml @@ -1,5 +1,5 @@ name: dart_internal -version: 0.2.10 +version: 0.2.11 description: >- This package is not intended for wide use. It provides a temporary API to solve the problem: "Given an object some generic type A, how do I construct an @@ -9,7 +9,7 @@ repository: https://github.com/dart-lang/sdk/tree/main/pkg/dart_internal environment: # Restrict the upper bound so that we can remove support for this in a later # version of the SDK without it being a breaking change. - sdk: ">=2.12.0 <3.4.0" + sdk: ">=2.12.0 <3.5.0" # Use 'any' constraints here; we get our versions from the DEPS file. dev_dependencies: diff --git a/pkg/linter/CHANGELOG.md b/pkg/linter/CHANGELOG.md index e7dc6a46d5ec..46f24daaf2fd 100644 --- a/pkg/linter/CHANGELOG.md +++ b/pkg/linter/CHANGELOG.md @@ -3,6 +3,25 @@ - new lint: `unintended_html_in_doc_comment` - new lint: `unnecessary_library_name` - new lint: `missing_code_block_language_in_doc_comment` +- update `noop_primitive_operations` to allow an empty string literal at the + beginning or end of adjacent string literals: + + ```dart + // LINT + var s = 'a' + '' + 'b'; + + // OK + var s = '' + 'a' + 'b'; + + // OK + var s = 'a' + 'b'; + ''; + ``` # 3.3.0 @@ -41,7 +60,7 @@ # 1.35.0 -- add new lints: +- add new lints: - `implicit_reopen` - `unnecessary_breaks` - `type_literal_in_constant_pattern` @@ -75,7 +94,7 @@ - update `only_throw_errors` to not report on values of type `Never` - update `prefer_mixin` to handle class mixins -- update `unnecessary_null_checks` to ignore `Future.value` and +- update `unnecessary_null_checks` to ignore `Future.value` and `Completer.complete` - fix `unnecessary_parenthesis` false positives on constant patterns - new lint: `invalid_case_patterns` @@ -88,7 +107,7 @@ # 1.33.0 - fix `unnecessary_parenthesis` false-positive with null-aware expressions -- fix `void_checks` to allow assignments of `Future?` to parameters +- fix `void_checks` to allow assignments of `Future?` to parameters typed `FutureOr?` - removed support for: - `enable_null_safety` @@ -100,7 +119,7 @@ - update `void_checks` to allow returning `Never` as void - new lint: `unnecessary_breaks` - fix `use_build_context_synchronously` in if conditions -- update `no_adjacent_strings_in_list` to support set literals and for- and +- update `no_adjacent_strings_in_list` to support set literals and for- and if-elements # 1.32.0 @@ -131,14 +150,14 @@ - new lint: `dangling_library_doc_comments` - fix `no_leading_underscores_for_local_identifiers` to not report super formals as local variables - fix `unnecessary_overrides` false negatives -- fix `cancel_subscriptions` for nullable fields +- fix `cancel_subscriptions` for nullable fields - new lint: `collection_methods_unrelated_type` - update `library_names` to support unnamed libraries - fix `unnecessary_parenthesis` support for as-expressions - fix `use_build_context_synchronously` to check for context property accesses - fix false positive in `comment_references` - improved unrelated type checks to handle enums and cascades -- fix `unnecessary_brace_in_string_interps` for `this` expressions +- fix `unnecessary_brace_in_string_interps` for `this` expressions - update `use_build_context_synchronously` for `BuildContext.mounted` - improve `flutter_style_todos` to handle more cases - fix `use_build_context_synchronously` to check for `BuildContext`s in named expressions @@ -176,7 +195,7 @@ # 1.27.0 -- fix `avoid_redundant_argument_values` when referencing required +- fix `avoid_redundant_argument_values` when referencing required parameters in legacy libraries - performance improvements for `use_late_for_private_fields_and_variables` - new lint: `use_string_in_part_of_directives` @@ -229,7 +248,7 @@ # 1.23.0 - fixed `no_leading_underscores_for_local_identifiers` - to lint local function declarations + to lint local function declarations - fixed `avoid_init_to_null` to correctly handle super initializing defaults that are non-null - updated `no_leading_underscores_for_local_identifiers` @@ -238,7 +257,7 @@ start with a digit - updated `require_trailing_commas` to handle functions in asserts and multi-line strings -- updated `unsafe_html` to allow assignments to +- updated `unsafe_html` to allow assignments to `img.src` - fixed `unnecessary_null_checks` to properly handle map literal entries @@ -267,7 +286,7 @@ with `key` super parameter initializers - fixed `use_super_parameters` false positive with field formal params -- updated `unnecessary_null_checks` and +- updated `unnecessary_null_checks` and `null_check_on_nullable_type_parameter` to handle list/set/map literals, and `yield` and `await` expressions @@ -285,7 +304,7 @@ - new lint: `use_colored_box` - performance improvements for `sort_constructors` - doc improvements for `always_use_package_imports`, - `avoid_print`, and `avoid_relative_lib_imports` + `avoid_print`, and `avoid_relative_lib_imports` - update `avoid_void_async` to skip `main` functions - update `prefer_final_parameters` to not super on super params - lint updates for enhanced-enums and super-initializer language @@ -296,7 +315,7 @@ # 1.18.0 - extend `camel_case_types` to cover enums -- fix `no_leading_underscores_for_local_identifiers` to not +- fix `no_leading_underscores_for_local_identifiers` to not mis-flag field formal parameters with default values - fix `prefer_function_declarations_over_variables` to not mis-flag non-final fields @@ -320,7 +339,7 @@ - updates to `secure_pubspec_urls` to check `issue_tracker` and `repository` entries - new lint: `conditional_uri_does_not_exist` -- performance improvements for +- performance improvements for `missing_whitespace_between_adjacent_strings` # 1.15.0 @@ -335,13 +354,13 @@ # 1.14.0 -- fix `omit_local_variable_types` to not flag a local type that is +- fix `omit_local_variable_types` to not flag a local type that is required for inference # 1.13.0 - allow `while (true) { ...}` in `literal_only_boolean_expressions` -- fixed `file_names` to report at the start of the file (not the entire +- fixed `file_names` to report at the start of the file (not the entire compilation unit) - fixed `prefer_collection_literals` named typed param false positive - control flow improvements for `use_build_context_synchronously` @@ -355,19 +374,19 @@ # 1.11.0 -- added support for constructor tear-offs to `avoid_redundant_argument_values`, +- added support for constructor tear-offs to `avoid_redundant_argument_values`, `unnecessary_lambdas`, and `unnecessary_parenthesis` - new lint: `unnecessary_constructor_name` to flag unnecessary uses of `.new` # 1.10.0 -- improved regular expression parsing performance for common checks +- improved regular expression parsing performance for common checks (`camel_case_types`, `file_names`, etc.) - (internal) migrated to analyzer 2.1.0 APIs -- fixed false positive in `use_build_context_synchronously` in awaits inside +- fixed false positive in `use_build_context_synchronously` in awaits inside anonymous functions - fixed `overridden_fields` false positive w/ static fields -- fixed false positive in `avoid_null_checks_in_equality_operators` w/ +- fixed false positive in `avoid_null_checks_in_equality_operators` w/ non-nullable params - fixed false positive for deferred imports in `prefer_const_constructors` @@ -399,7 +418,7 @@ - fixed `curly_braces_in_flow_control_structures` to properly flag terminating `else-if` blocks - improved `always_specify_types` to support type aliases -- fixed false positive in `unnecessary_string_interpolations` w/ nullable interpolated +- fixed false positive in `unnecessary_string_interpolations` w/ nullable interpolated strings - fixed false positive in `avoid_function_literals_in_foreach_calls` for nullable iterables @@ -417,7 +436,7 @@ # 1.7.0 - fixed case-sensitive false positive in `use_full_hex_values_for_flutter_colors` -- improved try-block and switch statement flow analysis for +- improved try-block and switch statement flow analysis for `use_build_context_synchronously` - updated `use_setters_to_change_properties` to only highlight a method name, not the entire body and doc comment @@ -430,7 +449,7 @@ # 1.6.1 -- reverted relaxation of `sort_child_properties_last` to allow for a +- reverted relaxation of `sort_child_properties_last` to allow for a trailing Widget in instance creations # 1.6.0 @@ -439,14 +458,14 @@ underscore - fixed false negative in `prefer_final_parameters` where first parameter is final -- improved `directives_ordering` sorting of directives with dot paths and +- improved `directives_ordering` sorting of directives with dot paths and dot-separated package names - relaxed `sort_child_properties_last` to allow for a trailing Widget in instance creations # 1.5.0 -- (internal) migrated to `SecurityLintCode` instead of deprecated +- (internal) migrated to `SecurityLintCode` instead of deprecated `SecurityLintCodeWithUniqueName` - (internal) fixed `avoid_types_as_parameter_names` to skip field formal parameters diff --git a/pkg/linter/lib/src/analyzer.dart b/pkg/linter/lib/src/analyzer.dart index 77291b4eab23..3ab7425d0833 100644 --- a/pkg/linter/lib/src/analyzer.dart +++ b/pkg/linter/lib/src/analyzer.dart @@ -34,6 +34,7 @@ export 'package:analyzer/src/lint/linter.dart' export 'package:analyzer/src/lint/pub.dart' show PSEntry, PubspecVisitor; export 'package:analyzer/src/lint/util.dart' show FileSpelunker; export 'package:analyzer/src/services/lint.dart' show lintRegistry; +export 'package:analyzer/src/utilities/extensions/ast.dart'; export 'package:analyzer/src/workspace/pub.dart' show PubPackage; const loggedAnalyzerErrorExitCode = 63; diff --git a/pkg/linter/lib/src/rules/noop_primitive_operations.dart b/pkg/linter/lib/src/rules/noop_primitive_operations.dart index 0627c95f73a2..74d23a70eec5 100644 --- a/pkg/linter/lib/src/rules/noop_primitive_operations.dart +++ b/pkg/linter/lib/src/rules/noop_primitive_operations.dart @@ -26,11 +26,26 @@ intValue.truncate(); string.toString(); string = 'hello\n' - 'world\n' - ''; // useless empty string + '' + 'world'; 'string with ${x.toString()}'; ``` + +Note that the empty string literals at the beginning or end of a string are +allowed, as they are typically used to format the string literal across multiple +lines: + +```dart +// OK +string = '' + 'hello\n' + 'world\n'; + +// OK +string = 'hello\n' + 'world\n' + ''; '''; class NoopPrimitiveOperations extends LintRule { @@ -69,7 +84,10 @@ class _Visitor extends SimpleAstVisitor { @override void visitAdjacentStrings(AdjacentStrings node) { - for (var literal in node.strings) { + // We allow empty string literals at the beginning or end of a string: + // https://github.com/dart-lang/sdk/issues/55541#issuecomment-2073437613 + for (var i = 1; i < node.strings.length - 1; i++) { + var literal = node.strings[i]; if (literal.stringValue?.isEmpty ?? false) { rule.reportLint(literal); } diff --git a/pkg/linter/lib/src/rules/use_build_context_synchronously.dart b/pkg/linter/lib/src/rules/use_build_context_synchronously.dart index f4cb61bcd65b..39df5df59ecb 100644 --- a/pkg/linter/lib/src/rules/use_build_context_synchronously.dart +++ b/pkg/linter/lib/src/rules/use_build_context_synchronously.dart @@ -959,7 +959,7 @@ class UseBuildContextSynchronously extends LintRule { void registerNodeProcessors( NodeLintRegistry registry, LinterContext context) { var unit = context.currentUnit.unit; - if (!context.inTestDir(unit)) { + if (!unit.inTestDir) { var visitor = _Visitor(this); registry.addMethodInvocation(this, visitor); registry.addInstanceCreationExpression(this, visitor); diff --git a/pkg/linter/lib/src/util/dart_type_utilities.dart b/pkg/linter/lib/src/util/dart_type_utilities.dart index 4b5f64c1bd4f..ebd77d4d26ad 100644 --- a/pkg/linter/lib/src/util/dart_type_utilities.dart +++ b/pkg/linter/lib/src/util/dart_type_utilities.dart @@ -7,7 +7,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/src/dart/element/type.dart'; // ignore: implementation_imports -import '../analyzer.dart'; +import '../analyzer.dart' hide AstNodeNullableExtension; import '../ast.dart'; import '../extensions.dart'; diff --git a/pkg/linter/test/rules/noop_primitive_operations_test.dart b/pkg/linter/test/rules/noop_primitive_operations_test.dart index ea06eb6d7f00..d2c21d931ac7 100644 --- a/pkg/linter/test/rules/noop_primitive_operations_test.dart +++ b/pkg/linter/test/rules/noop_primitive_operations_test.dart @@ -125,14 +125,37 @@ onPrint() { '''); } - test_string_adjacentBlankString() async { - await assertDiagnostics(r''' + test_string_adjacentBlankString_lintInMiddle() async { + await assertDiagnostics( + r''' +void f(String x) { + x = 'hello\n' '' 'world\n'; +} +''', + [ + lint(35, 2), + ], + ); + } + + test_string_adjacentBlankString_okAtEnd() async { + await assertNoDiagnostics( + r''' void f(String x) { x = 'hello\n' 'world\n' ''; } -''', [ - lint(45, 2), - ]); +''', + ); + } + + test_string_adjacentBlankString_okAtStart() async { + await assertNoDiagnostics( + r''' +void f(String x) { + x = '' 'hello\n' 'world\n'; +} +''', + ); } test_string_nullable_toString() async { diff --git a/pkg/linter/tool/checks/rules/no_solo_tests.dart b/pkg/linter/tool/checks/rules/no_solo_tests.dart index 869edf6fdd81..47be7a138326 100644 --- a/pkg/linter/tool/checks/rules/no_solo_tests.dart +++ b/pkg/linter/tool/checks/rules/no_solo_tests.dart @@ -39,7 +39,7 @@ class NoSoloTests extends LintRule { @override void registerNodeProcessors( NodeLintRegistry registry, LinterContext context) { - if (context.inTestDir(context.currentUnit.unit)) { + if (context.currentUnit.unit.inTestDir) { var visitor = _Visitor(this); registry.addMethodDeclaration(this, visitor); } diff --git a/runtime/tools/dartfuzz/flag_fuzzer.dart b/runtime/tools/dartfuzz/flag_fuzzer.dart index 9d33120b66f1..45b1204350b4 100644 --- a/runtime/tools/dartfuzz/flag_fuzzer.dart +++ b/runtime/tools/dartfuzz/flag_fuzzer.dart @@ -115,7 +115,29 @@ List someOf(List choices) { return result; } +// LUCI will kill recipe steps if they go 1200 seconds without any output. +const statusTimeout = Duration(minutes: 5); +int pendingTaskCount = 0; +late Timer pendingTimer; +Stopwatch stopwatch = new Stopwatch(); +taskStart() { + if (pendingTaskCount++ == 0) { + pendingTimer = new Timer.periodic(statusTimeout, (timer) { + print("$pendingTaskCount tasks still running after " + "${stopwatch.elapsed.inMinutes} minutes"); + }); + } +} + +taskEnd() { + if (--pendingTaskCount == 0) { + pendingTimer.cancel(); + } +} + test(int taskIndex) async { + taskStart(); + var buildDir = oneOf(buildDirs); var commands; @@ -196,7 +218,7 @@ test(int taskIndex) async { timer.cancel(); if (timedOut) { print("Timeout: $cmdline"); - return; + break; } else if (exitCode == 0) { print("Success: $cmdline"); process.stdout.drain(); @@ -215,12 +237,16 @@ test(int taskIndex) async { print("stderr:"); print(stderr); exitCode = 1; - return; + break; } } + + taskEnd(); } main() async { + stopwatch.start(); + await Directory("out/dartfuzz").create(); var executable = "out/ReleaseX64/dart"; diff --git a/tools/VERSION b/tools/VERSION index 0f4e7df0ac37..88b06d6113a1 100644 --- a/tools/VERSION +++ b/tools/VERSION @@ -27,5 +27,5 @@ CHANNEL dev MAJOR 3 MINOR 5 PATCH 0 -PRERELEASE 127 +PRERELEASE 128 PRERELEASE_PATCH 0