From 6af344c28f39839cefc22921ffbdee71ed551a67 Mon Sep 17 00:00:00 2001 From: hovadur Date: Thu, 7 Jan 2021 17:04:52 +0300 Subject: [PATCH 01/18] null safety --- benchmark/shortest_path_benchmark.dart | 4 +-- example/crawl_async_example.dart | 25 +++++++------ example/example.dart | 2 +- lib/src/crawl_async.dart | 4 +-- lib/src/shortest_path.dart | 32 ++++++++--------- lib/src/strongly_connected_components.dart | 22 ++++++++---- pubspec.yaml | 13 ++++--- test/crawl_async_test.dart | 7 ++-- test/shortest_path_test.dart | 37 +++++++------------- test/strongly_connected_components_test.dart | 10 +++--- test/utils/graph.dart | 18 +++++----- test/utils/utils.dart | 4 +-- 12 files changed, 88 insertions(+), 90 deletions(-) diff --git a/benchmark/shortest_path_benchmark.dart b/benchmark/shortest_path_benchmark.dart index 813c18b..099468a 100644 --- a/benchmark/shortest_path_benchmark.dart +++ b/benchmark/shortest_path_benchmark.dart @@ -21,7 +21,7 @@ void main() { } } - int minTicks; + int? minTicks; var maxIteration = 0; final testOutput = @@ -34,7 +34,7 @@ void main() { watch ..reset() ..start(); - final length = shortestPath(0, size - 1, (e) => graph[e] ?? []).length; + final length = shortestPath(0, size - 1, (e) => graph[e] ?? [])?.length; watch.stop(); assert(length == 4, '$length'); diff --git a/example/crawl_async_example.dart b/example/crawl_async_example.dart index ce4c3e3..35a63a7 100644 --- a/example/crawl_async_example.dart +++ b/example/crawl_async_example.dart @@ -26,9 +26,9 @@ Future main() async { print(allImports.map((s) => s.uri).toList()); } -AnalysisContext _analysisContext; +AnalysisContext? _analysisContext; -Future get analysisContext async { +Future get analysisContext async { if (_analysisContext == null) { var libUri = Uri.parse('package:graphs/'); var libPath = await pathForUri(libUri); @@ -39,25 +39,28 @@ Future get analysisContext async { throw StateError('Expected to find exactly one context root, got $roots'); } - _analysisContext = ContextBuilder().createContext(contextRoot: roots[0]); + final analysisContext = + ContextBuilder().createContext(contextRoot: roots[0]); + _analysisContext = analysisContext; + return analysisContext; } - return _analysisContext; + return null; } -Future> findImports(Uri from, Source source) async { - return source.unit.directives +Future?> findImports(Uri from, Source source) async { + return source.unit?.directives .whereType() .map((d) => d.uri.stringValue) .where((uri) => !uri.startsWith('dart:')) .map((import) => resolveImport(import, from)); } -Future parseUri(Uri uri) async { +Future parseUri(Uri uri) async { var path = await pathForUri(uri); - var analysisSession = (await analysisContext).currentSession; - var parseResult = analysisSession.getParsedUnit(path); - return parseResult.unit; + var analysisSession = (await analysisContext)?.currentSession; + var parseResult = analysisSession?.getParsedUnit(path); + return parseResult?.unit; } Future pathForUri(Uri uri) async { @@ -81,7 +84,7 @@ Uri resolveImport(String import, Uri from) { class Source { final Uri uri; - final CompilationUnit unit; + final CompilationUnit? unit; Source(this.uri, this.unit); } diff --git a/example/example.dart b/example/example.dart index 47aca61..2fbb8e1 100644 --- a/example/example.dart +++ b/example/example.dart @@ -41,7 +41,7 @@ void main() { }); var components = stronglyConnectedComponents( - graph.nodes.keys, (node) => graph.nodes[node]); + graph.nodes.keys, (node) => graph.nodes[node] ?? []); print(components); } diff --git a/lib/src/crawl_async.dart b/lib/src/crawl_async.dart index 2cf0f54..5a74170 100644 --- a/lib/src/crawl_async.dart +++ b/lib/src/crawl_async.dart @@ -34,7 +34,7 @@ final _empty = Future.value(null); /// have not completed. If the [edges] callback needs to be limited or throttled /// that must be done by wrapping it before calling [crawlAsync]. Stream crawlAsync(Iterable roots, FutureOr Function(K) readNode, - FutureOr> Function(K, V) edges) { + FutureOr?> Function(K, V) edges) { final crawl = _CrawlAsync(roots, readNode, edges)..run(); return crawl.result.stream; } @@ -43,7 +43,7 @@ class _CrawlAsync { final result = StreamController(); final FutureOr Function(K) readNode; - final FutureOr> Function(K, V) edges; + final FutureOr?> Function(K, V) edges; final Iterable roots; final _seen = HashSet(); diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index f0fd598..bb16b5c 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -24,12 +24,12 @@ import 'dart:collection'; /// /// If you supply one of [equals] or [hashCode], you should generally also to /// supply the other. -List shortestPath( +List? shortestPath( T start, T target, Iterable Function(T) edges, { - bool Function(T, T) equals, - int Function(T) hashCode, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) => _shortestPaths( start, @@ -61,8 +61,8 @@ List shortestPath( Map> shortestPaths( T start, Iterable Function(T) edges, { - bool Function(T, T) equals, - int Function(T) hashCode, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) => _shortestPaths( start, @@ -74,29 +74,29 @@ Map> shortestPaths( Map> _shortestPaths( T start, Iterable Function(T) edges, { - T target, - bool Function(T, T) equals, - int Function(T) hashCode, + T? target, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) { assert(start != null, '`start` cannot be null'); assert(edges != null, '`edges` cannot be null'); final distances = HashMap>(equals: equals, hashCode: hashCode); - distances[start] = List(0); + distances[start] = []; equals ??= _defaultEquals; - if (equals(start, target)) { + if (equals.call(start, target)) { return distances; } final toVisit = ListQueue()..add(start); - List bestOption; + List? bestOption; while (toVisit.isNotEmpty) { final current = toVisit.removeFirst(); final currentPath = distances[current]; - final currentPathLength = currentPath.length; + final currentPathLength = currentPath?.length ?? 0; if (bestOption != null && (currentPathLength + 1) >= bestOption.length) { // Skip any existing `toVisit` items that have no chance of being @@ -111,12 +111,12 @@ Map> _shortestPaths( assert(existingPath == null || existingPath.length <= (currentPathLength + 1)); - if (existingPath == null) { - final newOption = List(currentPathLength + 1) + if (existingPath == null && currentPath != null) { + final newOption = List.filled(currentPathLength + 1, edge) ..setRange(0, currentPathLength, currentPath) ..[currentPathLength] = edge; - if (equals(edge, target)) { + if (target != null && equals(edge, target)) { assert(bestOption == null || bestOption.length > newOption.length); bestOption = newOption; } @@ -134,4 +134,4 @@ Map> _shortestPaths( return distances; } -bool _defaultEquals(Object a, Object b) => a == b; +bool _defaultEquals(T a, T b) => a == b; diff --git a/lib/src/strongly_connected_components.dart b/lib/src/strongly_connected_components.dart index 26018b9..8648957 100644 --- a/lib/src/strongly_connected_components.dart +++ b/lib/src/strongly_connected_components.dart @@ -32,8 +32,8 @@ import 'dart:math' show min; List> stronglyConnectedComponents( Iterable nodes, Iterable Function(T) edges, { - bool Function(T, T) equals, - int Function(T) hashCode, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) { final result = >[]; final lowLinks = HashMap(equals: equals, hashCode: hashCode); @@ -52,12 +52,20 @@ List> stronglyConnectedComponents( lastVisited.addLast(node); onStack.add(node); // ignore: omit_local_variable_types - for (final T next in edges(node) ?? const []) { + for (final T next in edges(node)) { if (!indexes.containsKey(next)) { strongConnect(next); - lowLinks[node] = min(lowLinks[node], lowLinks[next]); + final n = lowLinks[node]; + final x = lowLinks[next]; + if (n != null && x != null) { + lowLinks[node] = min(n, x); + } } else if (onStack.contains(next)) { - lowLinks[node] = min(lowLinks[node], indexes[next]); + final n = lowLinks[node]; + final x = indexes[next]; + if (n != null && x != null) { + lowLinks[node] = min(n, x); + } } } if (lowLinks[node] == indexes[node]) { @@ -67,7 +75,7 @@ List> stronglyConnectedComponents( next = lastVisited.removeLast(); onStack.remove(next); component.add(next); - } while (!equals(next, node)); + } while (equals?.call(next, node) == false); result.add(component); } } @@ -78,4 +86,4 @@ List> stronglyConnectedComponents( return result; } -bool _defaultEquals(Object a, Object b) => a == b; +bool _defaultEquals(T a, T b) => a == b; diff --git a/pubspec.yaml b/pubspec.yaml index cf0075b..6bc2896 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,16 +1,15 @@ name: graphs -version: 0.2.1-dev +version: 0.3.0-nullsafety.0 description: Graph algorithms that operate on graphs in any representation homepage: https://github.com/dart-lang/graphs environment: - sdk: '>=2.2.0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dev_dependencies: - pedantic: ^1.3.0 - test: ^1.5.1 + pedantic: ^1.10.0-nullsafety.3 + test: ^1.16.0-nullsafety.13 # For examples - analyzer: '>=0.35.0 <0.41.0' - path: ^1.1.0 - pool: ^1.3.0 + path: ^1.8.0-nullsafety.3 + pool: ^1.5.0-nullsafety.3 diff --git a/test/crawl_async_test.dart b/test/crawl_async_test.dart index 4a0011b..7481264 100644 --- a/test/crawl_async_test.dart +++ b/test/crawl_async_test.dart @@ -2,16 +2,15 @@ // 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/test.dart'; - import 'package:graphs/graphs.dart'; +import 'package:test/test.dart'; import 'utils/graph.dart'; void main() { group('asyncCrawl', () { - Future> crawl( - Map> g, Iterable roots) { + Future> crawl( + Map?> g, Iterable roots) { var graph = AsyncGraph(g); return crawlAsync(roots, graph.readNode, graph.edges).toList(); } diff --git a/test/shortest_path_test.dart b/test/shortest_path_test.dart index 3e38067..f787ce3 100644 --- a/test/shortest_path_test.dart +++ b/test/shortest_path_test.dart @@ -24,10 +24,10 @@ void main() { '6': ['7'], }; - List getValues(String key) => graph[key] ?? []; + List getValues(String? key) => graph[key] ?? []; List getXValues(X key) => - graph[key.value]?.map((v) => X(v))?.toList() ?? []; + graph[key.value]?.map((v) => X(v)).toList() ?? []; test('null `start` throws AssertionError', () { expect(() => shortestPath(null, '1', getValues), @@ -36,30 +36,17 @@ void main() { _throwsAssertionError('`start` cannot be null')); }); - test('null `edges` throws AssertionError', () { - expect(() => shortestPath(1, 1, null), - _throwsAssertionError('`edges` cannot be null')); - expect(() => shortestPaths(1, null), - _throwsAssertionError('`edges` cannot be null')); - }); - test('null return value from `edges` throws', () { - expect(shortestPath(1, 1, (input) => null), [], - reason: 'self target short-circuits'); expect(shortestPath(1, 1, (input) => [null]), [], reason: 'self target short-circuits'); - expect(() => shortestPath(1, 2, (input) => null), throwsNoSuchMethodError); - - expect(() => shortestPaths(1, (input) => null), throwsNoSuchMethodError); - expect(() => shortestPath(1, 2, (input) => [null]), _throwsAssertionError('`edges` cannot return null values.')); expect(() => shortestPaths(1, (input) => [null]), _throwsAssertionError('`edges` cannot return null values.')); }); - void _singlePathTest(String from, String to, List expected) { + void _singlePathTest(String from, String? to, List? expected) { test('$from -> $to should be $expected (mapped)', () { expect( shortestPath(X(from), X(to), getXValues, @@ -127,7 +114,7 @@ void main() { final size = 1000; final graph = HashMap>(); - List resultForGraph() => + List? resultForGraph() => shortestPath(0, size - 1, (e) => graph[e] ?? const []); void addRandomEdge() { @@ -139,7 +126,7 @@ void main() { } } - List result; + List? result; // Add edges until there is a shortest path between `0` and `size - 1` do { @@ -156,10 +143,10 @@ void main() { do { expect(++count, lessThan(size * 5), reason: 'This loop should finish.'); addRandomEdge(); - final previousResultLength = result.length; + final previousResultLength = result?.length; result = resultForGraph(); expect(result, hasLength(lessThanOrEqualTo(previousResultLength))); - } while (result.length > 2); + } while ((result?.length ?? 0) > 2); expect(result, [275, 999]); @@ -173,11 +160,13 @@ void main() { final randomKey = graph.keys.elementAt(_rnd.nextInt(graph.length)); final list = graph[randomKey]; expect(list, isNotEmpty); - list.removeAt(_rnd.nextInt(list.length)); - if (list.isEmpty) { - graph.remove(randomKey); + if (list != null) { + list.removeAt(_rnd.nextInt(list.length)); + if (list.isEmpty) { + graph.remove(randomKey); + } } - final previousResultLength = result.length; + final previousResultLength = result?.length; result = resultForGraph(); if (result != null) { expect(result, hasLength(greaterThanOrEqualTo(previousResultLength))); diff --git a/test/strongly_connected_components_test.dart b/test/strongly_connected_components_test.dart index 6c3968d..11f36d1 100644 --- a/test/strongly_connected_components_test.dart +++ b/test/strongly_connected_components_test.dart @@ -12,8 +12,8 @@ void main() { group('strongly connected components', () { /// Run [stronglyConnectedComponents] on [g]. List> components( - Map> g, { - Iterable startNodes, + Map?> g, { + Iterable? startNodes, }) { final graph = Graph(g); return stronglyConnectedComponents( @@ -147,9 +147,9 @@ void main() { group('custom hashCode and equals', () { /// Run [stronglyConnectedComponents] on [g]. - List> components( - Map> g, { - Iterable startNodes, + List> components( + Map?> g, { + Iterable? startNodes, }) { final graph = BadGraph(g); diff --git a/test/utils/graph.dart b/test/utils/graph.dart index f14efe0..96200a2 100644 --- a/test/utils/graph.dart +++ b/test/utils/graph.dart @@ -8,11 +8,11 @@ import 'utils.dart'; /// A representation of a Graph since none is specified in `lib/`. class Graph { - final Map> _graph; + final Map?> _graph; Graph(this._graph); - List edges(String node) => _graph[node]; + List edges(String node) => _graph[node] ?? []; Iterable get allNodes => _graph.keys; } @@ -20,12 +20,12 @@ class Graph { class BadGraph { final Map> _graph; - BadGraph(Map> values) + BadGraph(Map?> values) : _graph = LinkedHashMap(equals: xEquals, hashCode: xHashCode) - ..addEntries(values.entries.map( - (e) => MapEntry(X(e.key), e?.value?.map((v) => X(v))?.toList()))); + ..addEntries(values.entries.map((e) => + MapEntry(X(e.key), e.value?.map((v) => X(v)).toList() ?? []))); - List edges(X node) => _graph[node]; + List edges(X node) => _graph[node] ?? []; Iterable get allNodes => _graph.keys; } @@ -33,12 +33,12 @@ class BadGraph { /// A representation of a Graph where keys can asynchronously be resolved to /// real values or to edges. class AsyncGraph { - final Map> graph; + final Map?> graph; AsyncGraph(this.graph); - Future readNode(String node) async => + Future readNode(String node) async => graph.containsKey(node) ? node : null; - Future> edges(String key, String node) async => graph[key]; + Future?> edges(String key, String? node) async => graph[key]; } diff --git a/test/utils/utils.dart b/test/utils/utils.dart index f4ed52b..981bad2 100644 --- a/test/utils/utils.dart +++ b/test/utils/utils.dart @@ -2,12 +2,12 @@ // 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. -bool xEquals(X a, X b) => a?.value == b?.value; +bool xEquals(X? a, X? b) => a?.value == b?.value; int xHashCode(X a) => a.value.hashCode; class X { - final String value; + final String? value; X(this.value); From 1a3dbe6434906c3229aa42ec0180717938e8061d Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 21 Jan 2021 00:31:15 -0800 Subject: [PATCH 02/18] Migrate to GitHub Actions (#53) * Fix List deprecation * Support old sdks --- .github/workflows/ci.yml | 64 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 22 ------------- lib/src/shortest_path.dart | 9 +++--- 3 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..687adbd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [2.3.0, dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: pub get + - name: Run VM tests + run: pub run test --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests + run: pub run test --platform chrome + if: always() && steps.install.outcome == 'success' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 44cbea1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: dart - -dart: - - dev - - 2.2.0 - -dart_task: - - test - - test -p chrome,firefox - - dartanalyzer: --fatal-infos --fatal-warnings . - -matrix: - include: - - dart: dev - dart_task: dartfmt - -branches: - only: [master] - -cache: - directories: - - $HOME/.pub-cache diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index bb16b5c..3905443 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -82,7 +82,7 @@ Map> _shortestPaths( assert(edges != null, '`edges` cannot be null'); final distances = HashMap>(equals: equals, hashCode: hashCode); - distances[start] = []; + distances[start] = const []; equals ??= _defaultEquals; if (equals.call(start, target)) { @@ -112,9 +112,10 @@ Map> _shortestPaths( existingPath.length <= (currentPathLength + 1)); if (existingPath == null && currentPath != null) { - final newOption = List.filled(currentPathLength + 1, edge) - ..setRange(0, currentPathLength, currentPath) - ..[currentPathLength] = edge; + final newOption = [ + ...currentPath, + edge, + ]; if (target != null && equals(edge, target)) { assert(bestOption == null || bestOption.length > newOption.length); From 06151117ac552828672574b6d967fda942cd7809 Mon Sep 17 00:00:00 2001 From: hovadur Date: Thu, 7 Jan 2021 17:04:52 +0300 Subject: [PATCH 03/18] null safety --- benchmark/shortest_path_benchmark.dart | 4 +-- example/crawl_async_example.dart | 25 +++++++------ example/example.dart | 2 +- lib/src/crawl_async.dart | 4 +-- lib/src/shortest_path.dart | 28 +++++++-------- lib/src/strongly_connected_components.dart | 22 ++++++++---- pubspec.yaml | 13 ++++--- test/crawl_async_test.dart | 7 ++-- test/shortest_path_test.dart | 37 +++++++------------- test/strongly_connected_components_test.dart | 10 +++--- test/utils/graph.dart | 18 +++++----- test/utils/utils.dart | 4 +-- 12 files changed, 86 insertions(+), 88 deletions(-) diff --git a/benchmark/shortest_path_benchmark.dart b/benchmark/shortest_path_benchmark.dart index 813c18b..099468a 100644 --- a/benchmark/shortest_path_benchmark.dart +++ b/benchmark/shortest_path_benchmark.dart @@ -21,7 +21,7 @@ void main() { } } - int minTicks; + int? minTicks; var maxIteration = 0; final testOutput = @@ -34,7 +34,7 @@ void main() { watch ..reset() ..start(); - final length = shortestPath(0, size - 1, (e) => graph[e] ?? []).length; + final length = shortestPath(0, size - 1, (e) => graph[e] ?? [])?.length; watch.stop(); assert(length == 4, '$length'); diff --git a/example/crawl_async_example.dart b/example/crawl_async_example.dart index ce4c3e3..35a63a7 100644 --- a/example/crawl_async_example.dart +++ b/example/crawl_async_example.dart @@ -26,9 +26,9 @@ Future main() async { print(allImports.map((s) => s.uri).toList()); } -AnalysisContext _analysisContext; +AnalysisContext? _analysisContext; -Future get analysisContext async { +Future get analysisContext async { if (_analysisContext == null) { var libUri = Uri.parse('package:graphs/'); var libPath = await pathForUri(libUri); @@ -39,25 +39,28 @@ Future get analysisContext async { throw StateError('Expected to find exactly one context root, got $roots'); } - _analysisContext = ContextBuilder().createContext(contextRoot: roots[0]); + final analysisContext = + ContextBuilder().createContext(contextRoot: roots[0]); + _analysisContext = analysisContext; + return analysisContext; } - return _analysisContext; + return null; } -Future> findImports(Uri from, Source source) async { - return source.unit.directives +Future?> findImports(Uri from, Source source) async { + return source.unit?.directives .whereType() .map((d) => d.uri.stringValue) .where((uri) => !uri.startsWith('dart:')) .map((import) => resolveImport(import, from)); } -Future parseUri(Uri uri) async { +Future parseUri(Uri uri) async { var path = await pathForUri(uri); - var analysisSession = (await analysisContext).currentSession; - var parseResult = analysisSession.getParsedUnit(path); - return parseResult.unit; + var analysisSession = (await analysisContext)?.currentSession; + var parseResult = analysisSession?.getParsedUnit(path); + return parseResult?.unit; } Future pathForUri(Uri uri) async { @@ -81,7 +84,7 @@ Uri resolveImport(String import, Uri from) { class Source { final Uri uri; - final CompilationUnit unit; + final CompilationUnit? unit; Source(this.uri, this.unit); } diff --git a/example/example.dart b/example/example.dart index 47aca61..2fbb8e1 100644 --- a/example/example.dart +++ b/example/example.dart @@ -41,7 +41,7 @@ void main() { }); var components = stronglyConnectedComponents( - graph.nodes.keys, (node) => graph.nodes[node]); + graph.nodes.keys, (node) => graph.nodes[node] ?? []); print(components); } diff --git a/lib/src/crawl_async.dart b/lib/src/crawl_async.dart index 2cf0f54..5a74170 100644 --- a/lib/src/crawl_async.dart +++ b/lib/src/crawl_async.dart @@ -34,7 +34,7 @@ final _empty = Future.value(null); /// have not completed. If the [edges] callback needs to be limited or throttled /// that must be done by wrapping it before calling [crawlAsync]. Stream crawlAsync(Iterable roots, FutureOr Function(K) readNode, - FutureOr> Function(K, V) edges) { + FutureOr?> Function(K, V) edges) { final crawl = _CrawlAsync(roots, readNode, edges)..run(); return crawl.result.stream; } @@ -43,7 +43,7 @@ class _CrawlAsync { final result = StreamController(); final FutureOr Function(K) readNode; - final FutureOr> Function(K, V) edges; + final FutureOr?> Function(K, V) edges; final Iterable roots; final _seen = HashSet(); diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index b58afb5..3905443 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -24,12 +24,12 @@ import 'dart:collection'; /// /// If you supply one of [equals] or [hashCode], you should generally also to /// supply the other. -List shortestPath( +List? shortestPath( T start, T target, Iterable Function(T) edges, { - bool Function(T, T) equals, - int Function(T) hashCode, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) => _shortestPaths( start, @@ -61,8 +61,8 @@ List shortestPath( Map> shortestPaths( T start, Iterable Function(T) edges, { - bool Function(T, T) equals, - int Function(T) hashCode, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) => _shortestPaths( start, @@ -74,9 +74,9 @@ Map> shortestPaths( Map> _shortestPaths( T start, Iterable Function(T) edges, { - T target, - bool Function(T, T) equals, - int Function(T) hashCode, + T? target, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) { assert(start != null, '`start` cannot be null'); assert(edges != null, '`edges` cannot be null'); @@ -85,18 +85,18 @@ Map> _shortestPaths( distances[start] = const []; equals ??= _defaultEquals; - if (equals(start, target)) { + if (equals.call(start, target)) { return distances; } final toVisit = ListQueue()..add(start); - List bestOption; + List? bestOption; while (toVisit.isNotEmpty) { final current = toVisit.removeFirst(); final currentPath = distances[current]; - final currentPathLength = currentPath.length; + final currentPathLength = currentPath?.length ?? 0; if (bestOption != null && (currentPathLength + 1) >= bestOption.length) { // Skip any existing `toVisit` items that have no chance of being @@ -111,13 +111,13 @@ Map> _shortestPaths( assert(existingPath == null || existingPath.length <= (currentPathLength + 1)); - if (existingPath == null) { + if (existingPath == null && currentPath != null) { final newOption = [ ...currentPath, edge, ]; - if (equals(edge, target)) { + if (target != null && equals(edge, target)) { assert(bestOption == null || bestOption.length > newOption.length); bestOption = newOption; } @@ -135,4 +135,4 @@ Map> _shortestPaths( return distances; } -bool _defaultEquals(Object a, Object b) => a == b; +bool _defaultEquals(T a, T b) => a == b; diff --git a/lib/src/strongly_connected_components.dart b/lib/src/strongly_connected_components.dart index 26018b9..8648957 100644 --- a/lib/src/strongly_connected_components.dart +++ b/lib/src/strongly_connected_components.dart @@ -32,8 +32,8 @@ import 'dart:math' show min; List> stronglyConnectedComponents( Iterable nodes, Iterable Function(T) edges, { - bool Function(T, T) equals, - int Function(T) hashCode, + bool Function(T?, T?)? equals, + int Function(T)? hashCode, }) { final result = >[]; final lowLinks = HashMap(equals: equals, hashCode: hashCode); @@ -52,12 +52,20 @@ List> stronglyConnectedComponents( lastVisited.addLast(node); onStack.add(node); // ignore: omit_local_variable_types - for (final T next in edges(node) ?? const []) { + for (final T next in edges(node)) { if (!indexes.containsKey(next)) { strongConnect(next); - lowLinks[node] = min(lowLinks[node], lowLinks[next]); + final n = lowLinks[node]; + final x = lowLinks[next]; + if (n != null && x != null) { + lowLinks[node] = min(n, x); + } } else if (onStack.contains(next)) { - lowLinks[node] = min(lowLinks[node], indexes[next]); + final n = lowLinks[node]; + final x = indexes[next]; + if (n != null && x != null) { + lowLinks[node] = min(n, x); + } } } if (lowLinks[node] == indexes[node]) { @@ -67,7 +75,7 @@ List> stronglyConnectedComponents( next = lastVisited.removeLast(); onStack.remove(next); component.add(next); - } while (!equals(next, node)); + } while (equals?.call(next, node) == false); result.add(component); } } @@ -78,4 +86,4 @@ List> stronglyConnectedComponents( return result; } -bool _defaultEquals(Object a, Object b) => a == b; +bool _defaultEquals(T a, T b) => a == b; diff --git a/pubspec.yaml b/pubspec.yaml index c27d475..6bc2896 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,16 +1,15 @@ name: graphs -version: 0.2.1-dev +version: 0.3.0-nullsafety.0 description: Graph algorithms that operate on graphs in any representation homepage: https://github.com/dart-lang/graphs environment: - sdk: '>=2.3.0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dev_dependencies: - pedantic: ^1.3.0 - test: ^1.5.1 + pedantic: ^1.10.0-nullsafety.3 + test: ^1.16.0-nullsafety.13 # For examples - analyzer: '>=0.35.0 <0.42.0' - path: ^1.1.0 - pool: ^1.3.0 + path: ^1.8.0-nullsafety.3 + pool: ^1.5.0-nullsafety.3 diff --git a/test/crawl_async_test.dart b/test/crawl_async_test.dart index 4a0011b..7481264 100644 --- a/test/crawl_async_test.dart +++ b/test/crawl_async_test.dart @@ -2,16 +2,15 @@ // 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/test.dart'; - import 'package:graphs/graphs.dart'; +import 'package:test/test.dart'; import 'utils/graph.dart'; void main() { group('asyncCrawl', () { - Future> crawl( - Map> g, Iterable roots) { + Future> crawl( + Map?> g, Iterable roots) { var graph = AsyncGraph(g); return crawlAsync(roots, graph.readNode, graph.edges).toList(); } diff --git a/test/shortest_path_test.dart b/test/shortest_path_test.dart index 3e38067..f787ce3 100644 --- a/test/shortest_path_test.dart +++ b/test/shortest_path_test.dart @@ -24,10 +24,10 @@ void main() { '6': ['7'], }; - List getValues(String key) => graph[key] ?? []; + List getValues(String? key) => graph[key] ?? []; List getXValues(X key) => - graph[key.value]?.map((v) => X(v))?.toList() ?? []; + graph[key.value]?.map((v) => X(v)).toList() ?? []; test('null `start` throws AssertionError', () { expect(() => shortestPath(null, '1', getValues), @@ -36,30 +36,17 @@ void main() { _throwsAssertionError('`start` cannot be null')); }); - test('null `edges` throws AssertionError', () { - expect(() => shortestPath(1, 1, null), - _throwsAssertionError('`edges` cannot be null')); - expect(() => shortestPaths(1, null), - _throwsAssertionError('`edges` cannot be null')); - }); - test('null return value from `edges` throws', () { - expect(shortestPath(1, 1, (input) => null), [], - reason: 'self target short-circuits'); expect(shortestPath(1, 1, (input) => [null]), [], reason: 'self target short-circuits'); - expect(() => shortestPath(1, 2, (input) => null), throwsNoSuchMethodError); - - expect(() => shortestPaths(1, (input) => null), throwsNoSuchMethodError); - expect(() => shortestPath(1, 2, (input) => [null]), _throwsAssertionError('`edges` cannot return null values.')); expect(() => shortestPaths(1, (input) => [null]), _throwsAssertionError('`edges` cannot return null values.')); }); - void _singlePathTest(String from, String to, List expected) { + void _singlePathTest(String from, String? to, List? expected) { test('$from -> $to should be $expected (mapped)', () { expect( shortestPath(X(from), X(to), getXValues, @@ -127,7 +114,7 @@ void main() { final size = 1000; final graph = HashMap>(); - List resultForGraph() => + List? resultForGraph() => shortestPath(0, size - 1, (e) => graph[e] ?? const []); void addRandomEdge() { @@ -139,7 +126,7 @@ void main() { } } - List result; + List? result; // Add edges until there is a shortest path between `0` and `size - 1` do { @@ -156,10 +143,10 @@ void main() { do { expect(++count, lessThan(size * 5), reason: 'This loop should finish.'); addRandomEdge(); - final previousResultLength = result.length; + final previousResultLength = result?.length; result = resultForGraph(); expect(result, hasLength(lessThanOrEqualTo(previousResultLength))); - } while (result.length > 2); + } while ((result?.length ?? 0) > 2); expect(result, [275, 999]); @@ -173,11 +160,13 @@ void main() { final randomKey = graph.keys.elementAt(_rnd.nextInt(graph.length)); final list = graph[randomKey]; expect(list, isNotEmpty); - list.removeAt(_rnd.nextInt(list.length)); - if (list.isEmpty) { - graph.remove(randomKey); + if (list != null) { + list.removeAt(_rnd.nextInt(list.length)); + if (list.isEmpty) { + graph.remove(randomKey); + } } - final previousResultLength = result.length; + final previousResultLength = result?.length; result = resultForGraph(); if (result != null) { expect(result, hasLength(greaterThanOrEqualTo(previousResultLength))); diff --git a/test/strongly_connected_components_test.dart b/test/strongly_connected_components_test.dart index 6c3968d..11f36d1 100644 --- a/test/strongly_connected_components_test.dart +++ b/test/strongly_connected_components_test.dart @@ -12,8 +12,8 @@ void main() { group('strongly connected components', () { /// Run [stronglyConnectedComponents] on [g]. List> components( - Map> g, { - Iterable startNodes, + Map?> g, { + Iterable? startNodes, }) { final graph = Graph(g); return stronglyConnectedComponents( @@ -147,9 +147,9 @@ void main() { group('custom hashCode and equals', () { /// Run [stronglyConnectedComponents] on [g]. - List> components( - Map> g, { - Iterable startNodes, + List> components( + Map?> g, { + Iterable? startNodes, }) { final graph = BadGraph(g); diff --git a/test/utils/graph.dart b/test/utils/graph.dart index f14efe0..96200a2 100644 --- a/test/utils/graph.dart +++ b/test/utils/graph.dart @@ -8,11 +8,11 @@ import 'utils.dart'; /// A representation of a Graph since none is specified in `lib/`. class Graph { - final Map> _graph; + final Map?> _graph; Graph(this._graph); - List edges(String node) => _graph[node]; + List edges(String node) => _graph[node] ?? []; Iterable get allNodes => _graph.keys; } @@ -20,12 +20,12 @@ class Graph { class BadGraph { final Map> _graph; - BadGraph(Map> values) + BadGraph(Map?> values) : _graph = LinkedHashMap(equals: xEquals, hashCode: xHashCode) - ..addEntries(values.entries.map( - (e) => MapEntry(X(e.key), e?.value?.map((v) => X(v))?.toList()))); + ..addEntries(values.entries.map((e) => + MapEntry(X(e.key), e.value?.map((v) => X(v)).toList() ?? []))); - List edges(X node) => _graph[node]; + List edges(X node) => _graph[node] ?? []; Iterable get allNodes => _graph.keys; } @@ -33,12 +33,12 @@ class BadGraph { /// A representation of a Graph where keys can asynchronously be resolved to /// real values or to edges. class AsyncGraph { - final Map> graph; + final Map?> graph; AsyncGraph(this.graph); - Future readNode(String node) async => + Future readNode(String node) async => graph.containsKey(node) ? node : null; - Future> edges(String key, String node) async => graph[key]; + Future?> edges(String key, String? node) async => graph[key]; } diff --git a/test/utils/utils.dart b/test/utils/utils.dart index f4ed52b..981bad2 100644 --- a/test/utils/utils.dart +++ b/test/utils/utils.dart @@ -2,12 +2,12 @@ // 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. -bool xEquals(X a, X b) => a?.value == b?.value; +bool xEquals(X? a, X? b) => a?.value == b?.value; int xHashCode(X a) => a.value.hashCode; class X { - final String value; + final String? value; X(this.value); From 8c08ac45701896bba48fac786bb3c58682a85fc4 Mon Sep 17 00:00:00 2001 From: hovadur Date: Tue, 9 Feb 2021 21:11:33 +0300 Subject: [PATCH 04/18] dev -> beta --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 687adbd..64e2f8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,13 +14,13 @@ env: jobs: # Check code formatting and static analysis on a single OS (linux) - # against Dart dev. + # against Dart beta. analyze: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - sdk: [dev] + sdk: [beta] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v0.3 @@ -38,7 +38,7 @@ jobs: # Run tests on a matrix consisting of two dimensions: # 1. OS: ubuntu-latest, (macos-latest, windows-latest) - # 2. release channel: dev + # 2. release channel: beta test: needs: analyze runs-on: ${{ matrix.os }} @@ -47,7 +47,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.3.0, dev] + sdk: [beta] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v0.3 From a6c49f1a7d9adf08335d5b93cbf7c9780385fa22 Mon Sep 17 00:00:00 2001 From: hovadur Date: Tue, 9 Feb 2021 21:50:23 +0300 Subject: [PATCH 05/18] analyzer null safety --- example/crawl_async_example.dart | 3 ++- pubspec.yaml | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/example/crawl_async_example.dart b/example/crawl_async_example.dart index 35a63a7..b0d1165 100644 --- a/example/crawl_async_example.dart +++ b/example/crawl_async_example.dart @@ -52,7 +52,8 @@ Future?> findImports(Uri from, Source source) async { return source.unit?.directives .whereType() .map((d) => d.uri.stringValue) - .where((uri) => !uri.startsWith('dart:')) + .whereType() + .where((uri) => uri.startsWith('dart:') == false) .map((import) => resolveImport(import, from)); } diff --git a/pubspec.yaml b/pubspec.yaml index 6bc2896..495a532 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,11 +5,12 @@ homepage: https://github.com/dart-lang/graphs environment: sdk: '>=2.12.0-0 <3.0.0' - +dependency_overrides: + analyzer: ^0.42.0-nullsafety.0 dev_dependencies: - pedantic: ^1.10.0-nullsafety.3 - test: ^1.16.0-nullsafety.13 + pedantic: ^1.10.0 + test: ^1.16.0 # For examples - path: ^1.8.0-nullsafety.3 - pool: ^1.5.0-nullsafety.3 + path: ^1.8.0 + pool: ^1.5.0 From b74808c5cc6f071ed8079be0dff348e3a05a91c8 Mon Sep 17 00:00:00 2001 From: hovadur Date: Tue, 9 Feb 2021 21:51:57 +0300 Subject: [PATCH 06/18] Remove the condition --- lib/src/shortest_path.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index 3905443..a41b71d 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -79,7 +79,6 @@ Map> _shortestPaths( int Function(T)? hashCode, }) { assert(start != null, '`start` cannot be null'); - assert(edges != null, '`edges` cannot be null'); final distances = HashMap>(equals: equals, hashCode: hashCode); distances[start] = const []; From 8658a2b42ae1c0734b3e5bf01965c9ae93f06bbd Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 19:39:50 +0300 Subject: [PATCH 07/18] review --- lib/src/shortest_path.dart | 2 +- lib/src/strongly_connected_components.dart | 2 +- pubspec.yaml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index a41b71d..847f735 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -84,7 +84,7 @@ Map> _shortestPaths( distances[start] = const []; equals ??= _defaultEquals; - if (equals.call(start, target)) { + if (equals(start, target)) { return distances; } diff --git a/lib/src/strongly_connected_components.dart b/lib/src/strongly_connected_components.dart index 8648957..1cb5feb 100644 --- a/lib/src/strongly_connected_components.dart +++ b/lib/src/strongly_connected_components.dart @@ -75,7 +75,7 @@ List> stronglyConnectedComponents( next = lastVisited.removeLast(); onStack.remove(next); component.add(next); - } while (equals?.call(next, node) == false); + } while (!equals!(next, node)); result.add(component); } } diff --git a/pubspec.yaml b/pubspec.yaml index 495a532..b018919 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,15 +1,15 @@ name: graphs -version: 0.3.0-nullsafety.0 +version: 0.3.0 description: Graph algorithms that operate on graphs in any representation homepage: https://github.com/dart-lang/graphs environment: sdk: '>=2.12.0-0 <3.0.0' -dependency_overrides: - analyzer: ^0.42.0-nullsafety.0 + dev_dependencies: pedantic: ^1.10.0 test: ^1.16.0 + analyzer: ^1.0.0 # For examples path: ^1.8.0 From 59a09919f038e3b2348316157a81ff7b12aa88e5 Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 20:10:26 +0300 Subject: [PATCH 08/18] fix --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b018919..ce28a46 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,8 +9,8 @@ environment: dev_dependencies: pedantic: ^1.10.0 test: ^1.16.0 - analyzer: ^1.0.0 # For examples path: ^1.8.0 pool: ^1.5.0 + analyzer: ^1.0.0 From df84a7b9455223ee8b6eabb194fcb3bf3234e32a Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 20:52:22 +0300 Subject: [PATCH 09/18] review --- example/crawl_async_example.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example/crawl_async_example.dart b/example/crawl_async_example.dart index b0d1165..c4e6dbb 100644 --- a/example/crawl_async_example.dart +++ b/example/crawl_async_example.dart @@ -28,7 +28,7 @@ Future main() async { AnalysisContext? _analysisContext; -Future get analysisContext async { +Future get analysisContext async { if (_analysisContext == null) { var libUri = Uri.parse('package:graphs/'); var libPath = await pathForUri(libUri); @@ -45,7 +45,7 @@ Future get analysisContext async { return analysisContext; } - return null; + return _analysisContext!; } Future?> findImports(Uri from, Source source) async { @@ -57,11 +57,11 @@ Future?> findImports(Uri from, Source source) async { .map((import) => resolveImport(import, from)); } -Future parseUri(Uri uri) async { +Future parseUri(Uri uri) async { var path = await pathForUri(uri); - var analysisSession = (await analysisContext)?.currentSession; - var parseResult = analysisSession?.getParsedUnit(path); - return parseResult?.unit; + var analysisSession = (await analysisContext).currentSession; + var parseResult = analysisSession.getParsedUnit(path); + return parseResult.unit; } Future pathForUri(Uri uri) async { From f354b05f00d987d8443ef0cf38539ae3460992a6 Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 21:24:32 +0300 Subject: [PATCH 10/18] just T --- example/crawl_async_example.dart | 6 +++--- lib/src/shortest_path.dart | 4 ++-- test/utils/graph.dart | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/example/crawl_async_example.dart b/example/crawl_async_example.dart index c4e6dbb..b9a8d23 100644 --- a/example/crawl_async_example.dart +++ b/example/crawl_async_example.dart @@ -48,8 +48,8 @@ Future get analysisContext async { return _analysisContext!; } -Future?> findImports(Uri from, Source source) async { - return source.unit?.directives +Future> findImports(Uri from, Source source) async { + return source.unit.directives .whereType() .map((d) => d.uri.stringValue) .whereType() @@ -85,7 +85,7 @@ Uri resolveImport(String import, Uri from) { class Source { final Uri uri; - final CompilationUnit? unit; + final CompilationUnit unit; Source(this.uri, this.unit); } diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index 847f735..97c9d23 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -75,7 +75,7 @@ Map> _shortestPaths( T start, Iterable Function(T) edges, { T? target, - bool Function(T?, T?)? equals, + bool Function(T, T)? equals, int Function(T)? hashCode, }) { assert(start != null, '`start` cannot be null'); @@ -84,7 +84,7 @@ Map> _shortestPaths( distances[start] = const []; equals ??= _defaultEquals; - if (equals(start, target)) { + if (target != null && equals(start, target)) { return distances; } diff --git a/test/utils/graph.dart b/test/utils/graph.dart index 96200a2..3bb1c06 100644 --- a/test/utils/graph.dart +++ b/test/utils/graph.dart @@ -40,5 +40,6 @@ class AsyncGraph { Future readNode(String node) async => graph.containsKey(node) ? node : null; - Future?> edges(String key, String? node) async => graph[key]; + Future> edges(String key, String? node) async => + graph[key] ?? []; } From 741d6d08184cd3b6192b3e5381caf3d6a6f7526a Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 21:27:18 +0300 Subject: [PATCH 11/18] ?currentPath --- lib/src/shortest_path.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index 97c9d23..d2c1a8c 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -110,9 +110,9 @@ Map> _shortestPaths( assert(existingPath == null || existingPath.length <= (currentPathLength + 1)); - if (existingPath == null && currentPath != null) { + if (existingPath == null) { final newOption = [ - ...currentPath, + ...?currentPath, edge, ]; From c02f50b380ea9ce92bcccceef86c628dc30f6753 Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 21:30:05 +0300 Subject: [PATCH 12/18] fix --- lib/src/shortest_path.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index d2c1a8c..85d64ac 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -28,7 +28,7 @@ List? shortestPath( T start, T target, Iterable Function(T) edges, { - bool Function(T?, T?)? equals, + bool Function(T, T)? equals, int Function(T)? hashCode, }) => _shortestPaths( @@ -61,7 +61,7 @@ List? shortestPath( Map> shortestPaths( T start, Iterable Function(T) edges, { - bool Function(T?, T?)? equals, + bool Function(T, T)? equals, int Function(T)? hashCode, }) => _shortestPaths( From 339abbb37f0e14450544b64cf52290b58cf3d795 Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 21:32:02 +0300 Subject: [PATCH 13/18] fix --- lib/src/strongly_connected_components.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/strongly_connected_components.dart b/lib/src/strongly_connected_components.dart index 1cb5feb..3faad03 100644 --- a/lib/src/strongly_connected_components.dart +++ b/lib/src/strongly_connected_components.dart @@ -32,7 +32,7 @@ import 'dart:math' show min; List> stronglyConnectedComponents( Iterable nodes, Iterable Function(T) edges, { - bool Function(T?, T?)? equals, + bool Function(T, T)? equals, int Function(T)? hashCode, }) { final result = >[]; From 2c454ea782f905d4e649a5242ae5bccc21b1f05f Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 21:36:24 +0300 Subject: [PATCH 14/18] fix _defaultEquals Co-authored-by: Jacob MacDonald --- lib/src/shortest_path.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/shortest_path.dart b/lib/src/shortest_path.dart index 85d64ac..3711960 100644 --- a/lib/src/shortest_path.dart +++ b/lib/src/shortest_path.dart @@ -134,4 +134,4 @@ Map> _shortestPaths( return distances; } -bool _defaultEquals(T a, T b) => a == b; +bool _defaultEquals(Object? a, Object? b) => a == b; From d78bd8045dff5c6197d00c55f43bafb7e0e72f8a Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 21:45:28 +0300 Subject: [PATCH 15/18] added some ! --- lib/src/strongly_connected_components.dart | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/src/strongly_connected_components.dart b/lib/src/strongly_connected_components.dart index 3faad03..abdd7bf 100644 --- a/lib/src/strongly_connected_components.dart +++ b/lib/src/strongly_connected_components.dart @@ -55,17 +55,9 @@ List> stronglyConnectedComponents( for (final T next in edges(node)) { if (!indexes.containsKey(next)) { strongConnect(next); - final n = lowLinks[node]; - final x = lowLinks[next]; - if (n != null && x != null) { - lowLinks[node] = min(n, x); - } + lowLinks[node] = min(lowLinks[node]!, lowLinks[next]!); } else if (onStack.contains(next)) { - final n = lowLinks[node]; - final x = indexes[next]; - if (n != null && x != null) { - lowLinks[node] = min(n, x); - } + lowLinks[node] = min(lowLinks[node]!, indexes[next]!); } } if (lowLinks[node] == indexes[node]) { From b13c4ebee5d142fbc7ac0302bf1937eb4f7be135 Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 22:05:22 +0300 Subject: [PATCH 16/18] Object? --- lib/src/strongly_connected_components.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/strongly_connected_components.dart b/lib/src/strongly_connected_components.dart index abdd7bf..e4ce1f2 100644 --- a/lib/src/strongly_connected_components.dart +++ b/lib/src/strongly_connected_components.dart @@ -78,4 +78,4 @@ List> stronglyConnectedComponents( return result; } -bool _defaultEquals(T a, T b) => a == b; +bool _defaultEquals(Object? a, Object? b) => a == b; From a19da5b30388752a62d0eb96d38f371311c0cebf Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 23:23:27 +0300 Subject: [PATCH 17/18] The documentation says that readNode is allowed to return null --- lib/src/crawl_async.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/crawl_async.dart b/lib/src/crawl_async.dart index 5a74170..9979d5f 100644 --- a/lib/src/crawl_async.dart +++ b/lib/src/crawl_async.dart @@ -33,7 +33,7 @@ final _empty = Future.value(null); /// Crawling is eager, so calls to [edges] may overlap with other calls that /// have not completed. If the [edges] callback needs to be limited or throttled /// that must be done by wrapping it before calling [crawlAsync]. -Stream crawlAsync(Iterable roots, FutureOr Function(K) readNode, +Stream crawlAsync(Iterable roots, FutureOr Function(K) readNode, FutureOr?> Function(K, V) edges) { final crawl = _CrawlAsync(roots, readNode, edges)..run(); return crawl.result.stream; @@ -42,7 +42,7 @@ Stream crawlAsync(Iterable roots, FutureOr Function(K) readNode, class _CrawlAsync { final result = StreamController(); - final FutureOr Function(K) readNode; + final FutureOr Function(K) readNode; final FutureOr?> Function(K, V) edges; final Iterable roots; From 291cf401dfddeb0345b1b4c822a46240831415c6 Mon Sep 17 00:00:00 2001 From: hovadur Date: Fri, 19 Feb 2021 23:46:52 +0300 Subject: [PATCH 18/18] removed the trick like ?? const [] --- lib/src/crawl_async.dart | 6 +++--- test/crawl_async_test.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/crawl_async.dart b/lib/src/crawl_async.dart index 9979d5f..821e296 100644 --- a/lib/src/crawl_async.dart +++ b/lib/src/crawl_async.dart @@ -34,7 +34,7 @@ final _empty = Future.value(null); /// have not completed. If the [edges] callback needs to be limited or throttled /// that must be done by wrapping it before calling [crawlAsync]. Stream crawlAsync(Iterable roots, FutureOr Function(K) readNode, - FutureOr?> Function(K, V) edges) { + FutureOr> Function(K, V) edges) { final crawl = _CrawlAsync(roots, readNode, edges)..run(); return crawl.result.stream; } @@ -43,7 +43,7 @@ class _CrawlAsync { final result = StreamController(); final FutureOr Function(K) readNode; - final FutureOr?> Function(K, V) edges; + final FutureOr> Function(K, V) edges; final Iterable roots; final _seen = HashSet(); @@ -69,7 +69,7 @@ class _CrawlAsync { if (value == null) return; if (result.isClosed) return; result.add(value); - var next = await edges(key, value) ?? const []; + var next = await edges(key, value); await Future.wait(next.map(_visit), eagerError: true); } diff --git a/test/crawl_async_test.dart b/test/crawl_async_test.dart index 7481264..4884c85 100644 --- a/test/crawl_async_test.dart +++ b/test/crawl_async_test.dart @@ -87,7 +87,7 @@ void main() { 'a': ['b'], }; var nodes = crawlAsync(['a'], (n) => n, - (k, n) => k == 'b' ? throw ArgumentError() : graph[k]); + (k, n) => k == 'b' ? throw ArgumentError() : graph[k]!); expect(nodes, emitsThrough(emitsError(isArgumentError))); }); @@ -96,7 +96,7 @@ void main() { 'a': ['b'], }; var nodes = crawlAsync(['a'], (n) => n == 'b' ? throw ArgumentError() : n, - (k, n) => graph[k]); + (k, n) => graph[k]!); expect(nodes, emitsThrough(emitsError(isArgumentError))); }); });