[go_router] Keep params in nested routes (#975)

diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index f015d5f..e08a182 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.0.3
+
+- Fixed a bug where params disappear when pushing a nested route.
+
 ## 3.0.2
 
 - Moves source to flutter/packages.
diff --git a/packages/go_router/lib/src/go_router_delegate.dart b/packages/go_router/lib/src/go_router_delegate.dart
index e4279df..38d2fee 100644
--- a/packages/go_router/lib/src/go_router_delegate.dart
+++ b/packages/go_router/lib/src/go_router_delegate.dart
@@ -338,11 +338,16 @@
         // get stack of route matches
         matches = _getLocRouteMatches(loc, extra: extra);
 
-        var params = <String, String>{};
+        // merge new params to keep params from previously matched paths, e.g.
+        // /family/:fid/person/:pid provides fid and pid to person/:pid
+        var previouslyMatchedParams = <String, String>{};
         for (final match in matches) {
-          // merge new params to keep params from previously matched paths, e.g.
-          // /family/:fid/person/:pid provides fid and pid to person/:pid
-          params = {...params, ...match.decodedParams};
+          assert(
+            !previouslyMatchedParams.keys.any(match.encodedParams.containsKey),
+            'Duplicated parameter names',
+          );
+          match.encodedParams.addAll(previouslyMatchedParams);
+          previouslyMatchedParams = match.encodedParams;
         }
 
         // check top route for redirect
@@ -356,7 +361,7 @@
               name: top.route.name,
               path: top.route.path,
               fullpath: top.fullpath,
-              params: params,
+              params: top.decodedParams,
               queryParams: top.queryParams,
               extra: extra,
             ),
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index 467e3be..1d65523 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,7 +1,7 @@
 name: go_router
 description: A declarative router for Flutter based on Navigation 2 supporting
   deep linking, data-driven routes and more
-version: 3.0.2
+version: 3.0.3
 repository: https://github.com/flutter/packages/tree/main/packages/go_router
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
 
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index 1c702f3..abb247f 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -1353,6 +1353,44 @@
       expect(page2.fid, 'f2');
       expect(page2.pid, 'p1');
     });
+
+    test('keep param in nested route', () {
+      final routes = [
+        GoRoute(
+          path: '/',
+          builder: (builder, state) => const HomeScreen(),
+        ),
+        GoRoute(
+          path: '/family/:fid',
+          builder: (builder, state) => FamilyScreen(state.params['fid']!),
+          routes: [
+            GoRoute(
+              path: 'person/:pid',
+              builder: (context, state) {
+                final fid = state.params['fid']!;
+                final pid = state.params['pid']!;
+
+                return PersonScreen(fid, pid);
+              },
+            ),
+          ],
+        ),
+      ];
+
+      final router = _router(routes);
+      const fid = "f1";
+      const pid = "p2";
+      final loc = "/family/$fid/person/$pid";
+
+      router.push(loc);
+      final matches = router.routerDelegate.matches;
+
+      expect(router.location, loc);
+      expect(matches, hasLength(2));
+      expect(router.screenFor(matches.last).runtimeType, PersonScreen);
+      expect(matches.last.decodedParams['fid'], fid);
+      expect(matches.last.decodedParams['pid'], pid);
+    });
   });
 
   group('refresh listenable', () {