Repository: passsy/kt.dart (dart)

Issue:
groupByTransform(_).getOrDefault(_, KtList.empty()) crashes always
As groupByTransform directly casts KtMutableMap,KtMutableList to KtMap,KtList. when `getOrDefault(key, KtList.empty())` is used on returned map from groupByTransform. It crashes with error `type 'EmptyList<Currency>' is not a subtype of type 'KtMutableList<Currency>' of 'defaultValue'`

Reproducible code 
```dart
main() {
  var usd = Currency(1, "USD");
  var inr = Currency(2, "INR");
  KtList<Currency> currency = listOf(usd, inr);
  KtList<CurrencyConversionData> conversion =
      listOf(CurrencyConversionData(1, 2));
  KtMap<Currency, KtList<Currency>> conversions =
      _toCurrencyMap(conversion, currency);

  print(conversions.getOrDefault(Currency(1, "USD"), KtList.empty())); // CRASH HERE (type 'EmptyList<Currency>' is not a subtype of type 'KtMutableList<Currency>' of 'defaultValue')
}

KtMap<Currency, KtList<Currency>> _toCurrencyMap(
    KtList<CurrencyConversionData> conversion, KtList<Currency> currency) {
  KtMap<int, Currency> currencyMap = currency.associateBy((cur) => cur.id);
  return conversion.groupByTransform(
    (conv) => currencyMap.get(conv.fromRef)!,
    (conv) => currencyMap.get(conv.toRef)!,
  );
}

class Currency {
  final int id;
  final String ticker;

  Currency(this.id, this.ticker);
}

class CurrencyConversionData {
  final int fromRef;
  final int toRef;

  CurrencyConversionData(this.fromRef, this.toRef);
}

```

Pull request description:
Fixes #139

Also:
- Change `KtMap.getOrDefault<T>` to return `T` instead of `T?`
- Fix CI

Patch:
diff --git a/lib/src/collection/impl/iterator.dart b/lib/src/collection/impl/iterator.dart
index 123e2359..aabe94f3 100644
--- a/lib/src/collection/impl/iterator.dart
+++ b/lib/src/collection/impl/iterator.dart
@@ -1,5 +1,4 @@
 import "package:kt_dart/collection.dart";
-import "package:kt_dart/src/collection/kt_iterator_mutable.dart";
 
 class InterOpKIterator<T> implements KtIterator<T> {
   InterOpKIterator(this.iterator) : _hasNext = iterator.moveNext() {
diff --git a/lib/src/collection/impl/map_empty.dart b/lib/src/collection/impl/map_empty.dart
index 20894d20..3d2e0fb8 100644
--- a/lib/src/collection/impl/map_empty.dart
+++ b/lib/src/collection/impl/map_empty.dart
@@ -25,7 +25,7 @@ class EmptyMap<K, V> extends Object implements KtMap<K, V> {
   V? get(K key) => null;
 
   @override
-  V? getOrDefault(K key, V defaultValue) => defaultValue;
+  V getOrDefault(K key, V defaultValue) => defaultValue;
 
   @override
   bool isEmpty() => true;
diff --git a/lib/src/collection/impl/map_mutable.dart b/lib/src/collection/impl/map_mutable.dart
index 361b1267..3f9d2c44 100644
--- a/lib/src/collection/impl/map_mutable.dart
+++ b/lib/src/collection/impl/map_mutable.dart
@@ -41,7 +41,7 @@ class DartMutableMap<K, V> extends Object implements KtMutableMap<K, V> {
   V? operator [](K key) => get(key);
 
   @override
-  V? getOrDefault(K key, V defaultValue) => _map[key] ?? defaultValue;
+  V getOrDefault(K key, V defaultValue) => _map[key] ?? defaultValue;
 
   @override
   bool isEmpty() => _map.isEmpty;
diff --git a/lib/src/collection/kt_iterable.dart b/lib/src/collection/kt_iterable.dart
index b987c3b9..2cc5eda8 100644
--- a/lib/src/collection/kt_iterable.dart
+++ b/lib/src/collection/kt_iterable.dart
@@ -1,7 +1,6 @@
 import "dart:math" as math;
 
 import "package:kt_dart/collection.dart";
-import "package:kt_dart/src/collection/comparisons.dart";
 import "package:kt_dart/src/util/errors.dart";
 
 /// Classes that inherit from this interface can be represented as a sequence of elements that can
@@ -695,7 +694,13 @@ extension KtIterableExtensions<T> on KtIterable<T> {
   ///
   /// The returned map preserves the entry iteration order of the keys produced from the original collection.
   KtMap<K, KtList<T>> groupBy<K>(K Function(T) keySelector) {
-    final groups = groupByTo(linkedMapFrom<K, KtMutableList<T>>(), keySelector);
+    final groups = linkedMapFrom<K, KtList<T>>();
+    for (final element in iter) {
+      final key = keySelector(element);
+      final list = KtMutableMapExtensions(groups)
+          .getOrPut(key, () => mutableListOf<T>()) as KtMutableList<T>;
+      list.add(element);
+    }
     return groups;
   }
 
@@ -706,8 +711,13 @@ extension KtIterableExtensions<T> on KtIterable<T> {
   /// The returned map preserves the entry iteration order of the keys produced from the original collection.
   KtMap<K, KtList<V>> groupByTransform<K, V>(
       K Function(T) keySelector, V Function(T) valueTransform) {
-    final groups = groupByToTransform(
-        linkedMapFrom<K, KtMutableList<V>>(), keySelector, valueTransform);
+    final groups = linkedMapFrom<K, KtList<V>>();
+    for (final element in iter) {
+      final key = keySelector(element);
+      final list = KtMutableMapExtensions(groups)
+          .getOrPut(key, () => mutableListOf<V>()) as KtMutableList<V>;
+      list.add(valueTransform(element));
+    }
     return groups;
   }
 
diff --git a/lib/src/collection/kt_map.dart b/lib/src/collection/kt_map.dart
index 0e136171..e08e0adb 100644
--- a/lib/src/collection/kt_map.dart
+++ b/lib/src/collection/kt_map.dart
@@ -51,7 +51,7 @@ abstract class KtMap<K, V> {
   V? operator [](K key);
 
   /// Returns the value corresponding to the given [key], or [defaultValue] if such a key is not present in the map.
-  V? getOrDefault(K key, V defaultValue);
+  V getOrDefault(K key, V defaultValue);
 
   // Views
   /// Returns a read-only [KtSet] of all keys in this map.


Test patch:
diff --git a/test/collection/dart_unmodifiable_set_view_test.dart b/test/collection/dart_unmodifiable_set_view_test.dart
index 84d8534a..26cda3fd 100644
--- a/test/collection/dart_unmodifiable_set_view_test.dart
+++ b/test/collection/dart_unmodifiable_set_view_test.dart
@@ -1,5 +1,5 @@
-import "package:test/test.dart";
 import "package:kt_dart/collection.dart";
+import "package:test/test.dart";
 
 import "../test/assert_dart.dart";
 
diff --git a/test/collection/iterable_extensions_test.dart b/test/collection/iterable_extensions_test.dart
index 4e556e48..211c6067 100644
--- a/test/collection/iterable_extensions_test.dart
+++ b/test/collection/iterable_extensions_test.dart
@@ -842,6 +842,21 @@ void testIterable(KtIterable<T> Function<T>() emptyIterable,
   });
 
   group("groupBy", () {
+    test("basic generic return type 100% matches", () {
+      final iterable = iterableOf(["paul", "peter", "john", "lisa"]);
+      final grouped = iterable.groupBy((it) => it.length);
+      // Fixes https://github.com/passsy/kt.dart/issues/139
+      expect(grouped.runtimeType.toString(), contains("<int, KtList<String>"));
+    });
+
+    test("valuetransform generic return type 100% matches", () {
+      final iterable = iterableOf(["paul", "peter", "john", "lisa"]);
+      final grouped = iterable.groupByTransform(
+          (it) => it.length, (it) => it.toUpperCase());
+      // Fixes https://github.com/passsy/kt.dart/issues/139
+      expect(grouped.runtimeType.toString(), contains("<int, KtList<String>"));
+    });
+
     if (ordered) {
       test("basic", () {
         final iterable = iterableOf(["paul", "peter", "john", "lisa"]);
diff --git a/test/collection/map_extensions_test.dart b/test/collection/map_extensions_test.dart
index 778db300..c56d1104 100644
--- a/test/collection/map_extensions_test.dart
+++ b/test/collection/map_extensions_test.dart
@@ -709,7 +709,7 @@ class ThirdPartyMap<K, V> implements KtMap<K, V> {
   V? operator [](K key) => get(key);
 
   @override
-  V? getOrDefault(K key, V defaultValue) => _map[key] ?? defaultValue;
+  V getOrDefault(K key, V defaultValue) => _map[key] ?? defaultValue;
 
   @override
   bool isEmpty() => _map.isEmpty;
diff --git a/test/issues/all_issues.dart b/test/issues/all_issues.dart
index 0be5210a..52368583 100644
--- a/test/issues/all_issues.dart
+++ b/test/issues/all_issues.dart
@@ -1,5 +1,7 @@
 import "issue_111.dart" as issue_111;
+import "issue_139.dart" as issue_139;
 
 void main() {
   issue_111.main();
+  issue_139.main();
 }
diff --git a/test/issues/issue_139.dart b/test/issues/issue_139.dart
new file mode 100644
index 00000000..0d1efe30
--- /dev/null
+++ b/test/issues/issue_139.dart
@@ -0,0 +1,44 @@
+import "package:kt_dart/kt.dart";
+import "package:test/test.dart";
+
+void main() {
+  // https://github.com/passsy/kt.dart/issues/139
+  test("issue #139", () {
+    final usd = Currency(1, "USD");
+    final inr = Currency(2, "INR");
+    final KtList<Currency> currency = listOf(usd, inr);
+    final KtList<CurrencyConversionData> conversion =
+        listOf(CurrencyConversionData(1, 2));
+    final KtMap<Currency, KtList<Currency>> conversions =
+        _toCurrencyMap(conversion, currency);
+
+    // CRASH HERE (type 'EmptyList<Currency>' is not a subtype of type 'KtMutableList<Currency>' of 'defaultValue')
+    final result =
+        conversions.getOrDefault(Currency(3, "EUR"), const KtList.empty());
+    expect(result, emptyList());
+  });
+}
+
+KtMap<Currency, KtList<Currency>> _toCurrencyMap(
+    KtList<CurrencyConversionData> conversion, KtList<Currency> currency) {
+  final KtMap<int, Currency> currencyMap =
+      currency.associateBy((cur) => cur.id);
+  return conversion.groupByTransform(
+    (conv) => currencyMap.get(conv.fromRef)!,
+    (conv) => currencyMap.get(conv.toRef)!,
+  );
+}
+
+class Currency {
+  final int id;
+  final String ticker;
+
+  Currency(this.id, this.ticker);
+}
+
+class CurrencyConversionData {
+  final int fromRef;
+  final int toRef;
+
+  CurrencyConversionData(this.fromRef, this.toRef);
+}
diff --git a/test/standard/standard_test.dart b/test/standard/standard_test.dart
index 9f53f41f..d1f3064a 100644
--- a/test/standard/standard_test.dart
+++ b/test/standard/standard_test.dart
@@ -1,5 +1,5 @@
-import 'package:test/test.dart';
 import 'package:kt_dart/kt.dart';
+import 'package:test/test.dart';
 
 import '../test/assert_dart.dart';