[cfe] Add LocalTypeParameterScope

This cleans up the LocalScope interface and the handling of named function expressions.

Change-Id: Id0432910a9e65d8ae966dfab67c66248639d241a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419842
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart b/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
index 59d81bb..0c08b0f 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/forwarding_listener.dart
@@ -966,8 +966,9 @@
   }
 
   @override
-  void endFunctionName(Token beginToken, Token token) {
-    listener?.endFunctionName(beginToken, token);
+  void endFunctionName(
+      Token beginToken, Token token, bool isFunctionExpression) {
+    listener?.endFunctionName(beginToken, token, isFunctionExpression);
   }
 
   @override
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart b/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
index 187726a..08b9b65 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
@@ -812,7 +812,23 @@
 
   void beginFunctionName(Token token) {}
 
-  void endFunctionName(Token beginToken, Token token) {
+  /// The end of the function name in either a local function declaration, like
+  /// 'local' in:
+  ///
+  ///     void m() {
+  ///       void local() {}
+  ///     }
+  ///
+  /// or an erroneous function expression, like 'local' in:
+  ///
+  ///     void m() {
+  ///       var f = void local() {};
+  ///     }
+  ///
+  /// The boolean [isFunctionExpression] indicates that we are in the latter
+  /// case.
+  void endFunctionName(
+      Token beginToken, Token token, bool isFunctionExpression) {
     logEvent("FunctionName");
   }
 
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
index 7654aeb..814b3fc 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
@@ -5222,7 +5222,7 @@
       reportRecoverableError(
           beforeName.next!, codes.messageNamedFunctionExpression);
     }
-    listener.endFunctionName(begin, token);
+    listener.endFunctionName(begin, token, isFunctionExpression);
     token = parseFormalParametersRequiredOpt(formals, MemberKind.Local);
     token = parseInitializersOpt(token);
     token = parseAsyncOptBody(
diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart
index 60032f2..19529e6 100644
--- a/pkg/analyzer/lib/src/fasta/ast_builder.dart
+++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart
@@ -2105,7 +2105,8 @@
   }
 
   @override
-  void endFunctionName(Token beginToken, Token token) {
+  void endFunctionName(
+      Token beginToken, Token token, bool isFunctionExpression) {
     debugEvent("FunctionName");
   }
 
diff --git a/pkg/analyzer/test/generated/parser_fasta_listener.dart b/pkg/analyzer/test/generated/parser_fasta_listener.dart
index a2f7b13..ecb79ff 100644
--- a/pkg/analyzer/test/generated/parser_fasta_listener.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_listener.dart
@@ -981,9 +981,9 @@
   }
 
   @override
-  void endFunctionName(Token beginToken, Token token) {
+  void endFunctionName(Token beginToken, Token token, bool isFunctionExpression) {
     end('FunctionName');
-    super.endFunctionName(beginToken, token);
+    super.endFunctionName(beginToken, token, isFunctionExpression);
   }
 
   @override
diff --git a/pkg/front_end/lib/src/base/local_scope.dart b/pkg/front_end/lib/src/base/local_scope.dart
index 8e0e30f..cca9d92 100644
--- a/pkg/front_end/lib/src/base/local_scope.dart
+++ b/pkg/front_end/lib/src/base/local_scope.dart
@@ -4,6 +4,7 @@
 
 import '../builder/builder.dart';
 import '../builder/declaration_builders.dart';
+import '../builder/variable_builder.dart';
 import 'scope.dart';
 
 abstract class LocalScope implements LookupScope {
@@ -27,9 +28,7 @@
   /// If name was used previously in this scope, this method returns the read
   /// offsets which can be used for reporting a compile-time error about
   /// [name] being used before its declared.
-  List<int>? declare(String name, Builder builder);
-
-  void addLocalVariable(String name, Builder builder);
+  List<int>? declare(String name, VariableBuilder builder);
 
   @override
   Builder? lookupGetable(String name, int charOffset, Uri fileUri);
@@ -117,7 +116,7 @@
 
   /// Names declared in this scope.
   @override
-  Map<String, Builder>? _local;
+  Map<String, VariableBuilder>? _local;
 
   @override
   Map<String, List<int>>? usedNames;
@@ -128,12 +127,7 @@
   LocalScopeImpl(this._parent, this.kind, this.classNameOrDebugName);
 
   @override
-  void addLocalVariable(String name, Builder builder) {
-    (_local ??= {})[name] = builder;
-  }
-
-  @override
-  List<int>? declare(String name, Builder builder) {
+  List<int>? declare(String name, VariableBuilder builder) {
     List<int>? previousOffsets = usedNames?[name];
     if (previousOffsets != null && previousOffsets.isNotEmpty) {
       return previousOffsets;
@@ -157,12 +151,7 @@
 
 mixin ImmutableLocalScopeMixin implements LocalScope {
   @override
-  void addLocalVariable(String name, Builder builder) {
-    throw new UnsupportedError('$runtimeType($kind).addLocalMember');
-  }
-
-  @override
-  List<int>? declare(String name, Builder builder) {
+  List<int>? declare(String name, VariableBuilder builder) {
     throw new UnsupportedError('$runtimeType($kind).declare');
   }
 
@@ -171,6 +160,34 @@
   Map<String, List<int>>? get usedNames => null;
 }
 
+final class LocalTypeParameterScope extends BaseLocalScope
+    with LookupScopeMixin, ImmutableLocalScopeMixin, LocalScopeMixin {
+  @override
+  final LocalScope? _parent;
+  @override
+  final ScopeKind kind;
+  @override
+  final Map<String, TypeParameterBuilder>? _local;
+
+  final String _debugName;
+
+  LocalTypeParameterScope(
+      {required this.kind,
+      LocalScope? parent,
+      Map<String, TypeParameterBuilder>? local,
+      required String debugName})
+      : _parent = parent,
+        _local = local,
+        _debugName = debugName;
+
+  @override
+  String get classNameOrDebugName => _debugName;
+
+  @override
+  String toString() =>
+      "$runtimeType(${kind}, $classNameOrDebugName, ${_local?.keys})";
+}
+
 final class FixedLocalScope extends BaseLocalScope
     with LookupScopeMixin, ImmutableLocalScopeMixin, LocalScopeMixin {
   @override
diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart
index 0b98cad..d3e9e98 100644
--- a/pkg/front_end/lib/src/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/kernel/body_builder.dart
@@ -5085,41 +5085,49 @@
   void enterNominalVariablesScope(
       List<NominalParameterBuilder>? nominalVariableBuilders) {
     debugEvent("enterNominalVariableScope");
-    enterLocalScope(_localScope.createNestedScope(
-        debugName: "function-type scope", kind: ScopeKind.typeParameters));
+    Map<String, TypeParameterBuilder> typeParameters = {};
     if (nominalVariableBuilders != null) {
       for (NominalParameterBuilder builder in nominalVariableBuilders) {
         if (builder.isWildcard) continue;
         String name = builder.name;
-        Builder? existing = _localScope.lookupLocalVariable(name);
+        TypeParameterBuilder? existing = typeParameters[name];
         if (existing == null) {
-          _localScope.addLocalVariable(name, builder);
+          typeParameters[name] = builder;
         } else {
           // Coverage-ignore-block(suite): Not run.
           reportDuplicatedDeclaration(existing, name, builder.fileOffset);
         }
       }
     }
+    enterLocalScope(new LocalTypeParameterScope(
+        local: typeParameters,
+        parent: _localScope,
+        debugName: "local function type parameter scope",
+        kind: ScopeKind.typeParameters));
   }
 
   void enterStructuralVariablesScope(
       List<StructuralParameterBuilder>? structuralVariableBuilders) {
     debugEvent("enterStructuralVariableScope");
-    enterLocalScope(_localScope.createNestedScope(
-        debugName: "function-type scope", kind: ScopeKind.typeParameters));
+    Map<String, TypeParameterBuilder> typeParameters = {};
     if (structuralVariableBuilders != null) {
       for (StructuralParameterBuilder builder in structuralVariableBuilders) {
         if (builder.isWildcard) continue;
         String name = builder.name;
-        Builder? existing = _localScope.lookupLocalVariable(name);
+        TypeParameterBuilder? existing = typeParameters[name];
         if (existing == null) {
-          _localScope.addLocalVariable(name, builder);
+          typeParameters[name] = builder;
         } else {
           // Coverage-ignore-block(suite): Not run.
           reportDuplicatedDeclaration(existing, name, builder.fileOffset);
         }
       }
     }
+    enterLocalScope(new LocalTypeParameterScope(
+        local: typeParameters,
+        parent: _localScope,
+        debugName: "function-type scope",
+        kind: ScopeKind.typeParameters));
   }
 
   @override
@@ -7273,7 +7281,8 @@
   void handleNamedRecordField(Token colon) => handleNamedArgument(colon);
 
   @override
-  void endFunctionName(Token beginToken, Token token) {
+  void endFunctionName(
+      Token beginToken, Token token, bool isFunctionExpression) {
     debugEvent("FunctionName");
     Identifier name = pop() as Identifier;
     Token nameToken = name.token;
@@ -7290,20 +7299,24 @@
         isLocalFunction: true,
         isWildcard: isWildcard)
       ..fileOffset = name.nameOffset;
-    // TODO(ahe): Why are we looking up in local scope, but declaring in parent
-    // scope?
-    Builder? existing = _localScope.lookupLocalVariable(name.name);
-    if (existing != null) {
-      // Coverage-ignore-block(suite): Not run.
-      reportDuplicatedDeclaration(existing, name.name, name.nameOffset);
-    }
     push(new FunctionDeclarationImpl(
         variable,
         // The real function node is created later.
         dummyFunctionNode)
       ..fileOffset = beginToken.charOffset);
     if (!(libraryFeatures.wildcardVariables.isEnabled && variable.isWildcard)) {
-      declareVariable(variable, _localScopes.previous);
+      // The local scope stack contains a type parameter scope for the local
+      // function on top of the scope for the block in which the local function
+      // declaration occurs. So for a local function declaration, we add the
+      // declaration to the previous scope, i.e. the block scope.
+      //
+      // For a named function expression, a nested scope is created to hold the
+      // name, so that it doesn't pollute the block scope (the named function
+      // expression is erroneous and should introduce the name in the scope) and
+      // we therefore use the current scope in this case.
+      LocalScope scope =
+          isFunctionExpression ? _localScope : _localScopes.previous;
+      declareVariable(variable, scope);
     }
   }
 
diff --git a/pkg/front_end/lib/src/util/parser_ast_helper.dart b/pkg/front_end/lib/src/util/parser_ast_helper.dart
index a5296cd..3f57e3e 100644
--- a/pkg/front_end/lib/src/util/parser_ast_helper.dart
+++ b/pkg/front_end/lib/src/util/parser_ast_helper.dart
@@ -1054,9 +1054,12 @@
   }
 
   @override
-  void endFunctionName(Token beginToken, Token token) {
+  void endFunctionName(
+      Token beginToken, Token token, bool isFunctionExpression) {
     FunctionNameEnd data = new FunctionNameEnd(ParserAstType.END,
-        beginToken: beginToken, token: token);
+        beginToken: beginToken,
+        token: token,
+        isFunctionExpression: isFunctionExpression);
     seen(data);
   }
 
@@ -5373,15 +5376,19 @@
 class FunctionNameEnd extends ParserAstNode {
   final Token beginToken;
   final Token token;
+  final bool isFunctionExpression;
 
   FunctionNameEnd(ParserAstType type,
-      {required this.beginToken, required this.token})
+      {required this.beginToken,
+      required this.token,
+      required this.isFunctionExpression})
       : super("FunctionName", type);
 
   @override
   Map<String, Object?> get deprecatedArguments => {
         "beginToken": beginToken,
         "token": token,
+        "isFunctionExpression": isFunctionExpression,
       };
 
   @override
diff --git a/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.expect b/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.expect
index ae68e64..f431d52 100644
--- a/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.expect
+++ b/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.expect
@@ -818,7 +818,7 @@
           handleType(await, null)
           beginFunctionName(foo)
             handleIdentifier(foo, localFunctionDeclaration)
-          endFunctionName(await, ()
+          endFunctionName(await, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(int)
             endMetadataStar(0)
@@ -855,7 +855,7 @@
           handleType(await, null)
           beginFunctionName(bar)
             handleIdentifier(bar, localFunctionDeclaration)
-          endFunctionName(await, ()
+          endFunctionName(await, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(await)
             endMetadataStar(0)
diff --git a/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.intertwined.expect b/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.intertwined.expect
index 9256ebc..f7ca385 100644
--- a/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/error_recovery/issue_49116.dart.intertwined.expect
@@ -2019,7 +2019,7 @@
                     listener: beginFunctionName(foo)
                     ensureIdentifier(await, localFunctionDeclaration)
                       listener: handleIdentifier(foo, localFunctionDeclaration)
-                    listener: endFunctionName(await, ()
+                    listener: endFunctionName(await, (, false)
                     parseFormalParametersRequiredOpt(foo, MemberKind.Local)
                       parseFormalParametersRest((, MemberKind.Local)
                         listener: beginFormalParameters((, MemberKind.Local)
@@ -2096,7 +2096,7 @@
                     listener: beginFunctionName(bar)
                     ensureIdentifier(await, localFunctionDeclaration)
                       listener: handleIdentifier(bar, localFunctionDeclaration)
-                    listener: endFunctionName(await, ()
+                    listener: endFunctionName(await, (, false)
                     parseFormalParametersRequiredOpt(bar, MemberKind.Local)
                       parseFormalParametersRest((, MemberKind.Local)
                         listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.expect b/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.expect
index 5b0b74c..7ca74e7 100644
--- a/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.expect
+++ b/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.expect
@@ -96,7 +96,7 @@
           handleVoidKeyword(void)
           beginFunctionName(writeMessage)
             handleIdentifier(writeMessage, localFunctionDeclaration)
-          endFunctionName(void, ()
+          endFunctionName(void, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(String)
             endMetadataStar(0)
diff --git a/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.intertwined.expect b/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.intertwined.expect
index b6c710b..f88c864 100644
--- a/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/error_recovery/issue_49477.dart.intertwined.expect
@@ -200,7 +200,7 @@
                     listener: beginFunctionName(writeMessage)
                     ensureIdentifier(void, localFunctionDeclaration)
                       listener: handleIdentifier(writeMessage, localFunctionDeclaration)
-                    listener: endFunctionName(void, ()
+                    listener: endFunctionName(void, (, false)
                     parseFormalParametersRequiredOpt(writeMessage, MemberKind.Local)
                       parseFormalParametersRest((, MemberKind.Local)
                         listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.expect b/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.expect
index 610bad0..7b4daa8 100644
--- a/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.expect
+++ b/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.expect
@@ -91,7 +91,7 @@
           handleNoType(;)
           beginFunctionName(getNumber)
             handleIdentifier(getNumber, localFunctionDeclaration)
-          endFunctionName(getNumber, ()
+          endFunctionName(getNumber, (, false)
           beginFormalParameters((, MemberKind.Local)
           endFormalParameters(0, (, ), MemberKind.Local)
           handleNoInitializers()
diff --git a/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.intertwined.expect b/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.intertwined.expect
index c9bd30b..ee4bb27 100644
--- a/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/error_recovery/issue_49477_prime.dart.intertwined.expect
@@ -196,7 +196,7 @@
                   listener: beginFunctionName(getNumber)
                   ensureIdentifier(;, localFunctionDeclaration)
                     listener: handleIdentifier(getNumber, localFunctionDeclaration)
-                  listener: endFunctionName(getNumber, ()
+                  listener: endFunctionName(getNumber, (, false)
                   parseFormalParametersRequiredOpt(getNumber, MemberKind.Local)
                     parseFormalParametersRest((, MemberKind.Local)
                       listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.expect b/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.expect
index 0aaa65f..dcd04bf 100644
--- a/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.expect
+++ b/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.expect
@@ -46,7 +46,7 @@
           handleNoType(})
           beginFunctionName(onX)
             handleIdentifier(onX, localFunctionDeclaration)
-          endFunctionName(onX, ()
+          endFunctionName(onX, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(e)
             endMetadataStar(0)
diff --git a/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.intertwined.expect b/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.intertwined.expect
index a4c0136..6ed3d52 100644
--- a/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/general/call_on_after_try_block2_prime.dart.intertwined.expect
@@ -98,7 +98,7 @@
                   listener: beginFunctionName(onX)
                   ensureIdentifier(}, localFunctionDeclaration)
                     listener: handleIdentifier(onX, localFunctionDeclaration)
-                  listener: endFunctionName(onX, ()
+                  listener: endFunctionName(onX, (, false)
                   parseFormalParametersRequiredOpt(onX, MemberKind.Local)
                     parseFormalParametersRest((, MemberKind.Local)
                       listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/parser_testcases/general/function_declaration.dart.expect b/pkg/front_end/parser_testcases/general/function_declaration.dart.expect
index 0aba260..5c237ec 100644
--- a/pkg/front_end/parser_testcases/general/function_declaration.dart.expect
+++ b/pkg/front_end/parser_testcases/general/function_declaration.dart.expect
@@ -17,7 +17,7 @@
           handleNoType({)
           beginFunctionName(local)
             handleIdentifier(local, localFunctionDeclaration)
-          endFunctionName(local, ()
+          endFunctionName(local, (, false)
           beginFormalParameters((, MemberKind.Local)
           endFormalParameters(0, (, ), MemberKind.Local)
           handleNoInitializers()
diff --git a/pkg/front_end/parser_testcases/general/function_declaration.dart.intertwined.expect b/pkg/front_end/parser_testcases/general/function_declaration.dart.intertwined.expect
index abf2b04..401fa78 100644
--- a/pkg/front_end/parser_testcases/general/function_declaration.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/general/function_declaration.dart.intertwined.expect
@@ -40,7 +40,7 @@
                   listener: beginFunctionName(local)
                   ensureIdentifier({, localFunctionDeclaration)
                     listener: handleIdentifier(local, localFunctionDeclaration)
-                  listener: endFunctionName(local, ()
+                  listener: endFunctionName(local, (, false)
                   parseFormalParametersRequiredOpt(local, MemberKind.Local)
                     parseFormalParametersRest((, MemberKind.Local)
                       listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/parser_testcases/patterns/const_patterns.dart.expect b/pkg/front_end/parser_testcases/patterns/const_patterns.dart.expect
index 73ee566..1a50caa 100644
--- a/pkg/front_end/parser_testcases/patterns/const_patterns.dart.expect
+++ b/pkg/front_end/parser_testcases/patterns/const_patterns.dart.expect
@@ -931,7 +931,7 @@
                           beginFunctionName(fun)
                             handleIdentifier(fun, localFunctionDeclaration)
                             handleRecoverableError(NamedFunctionExpression, fun, fun)
-                          endFunctionName(void, ()
+                          endFunctionName(void, (, true)
                           beginFormalParameters((, MemberKind.Local)
                           endFormalParameters(0, (, ), MemberKind.Local)
                           handleNoInitializers()
diff --git a/pkg/front_end/parser_testcases/patterns/const_patterns.dart.intertwined.expect b/pkg/front_end/parser_testcases/patterns/const_patterns.dart.intertwined.expect
index 8d07e7e..0da519e 100644
--- a/pkg/front_end/parser_testcases/patterns/const_patterns.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/patterns/const_patterns.dart.intertwined.expect
@@ -1559,7 +1559,7 @@
                                             listener: handleIdentifier(fun, localFunctionDeclaration)
                                           reportRecoverableError(fun, NamedFunctionExpression)
                                             listener: handleRecoverableError(NamedFunctionExpression, fun, fun)
-                                          listener: endFunctionName(void, ()
+                                          listener: endFunctionName(void, (, true)
                                           parseFormalParametersRequiredOpt(fun, MemberKind.Local)
                                             parseFormalParametersRest((, MemberKind.Local)
                                               listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/parser_testcases/record/record_type_01.dart.expect b/pkg/front_end/parser_testcases/record/record_type_01.dart.expect
index 91a0150..b5b4017 100644
--- a/pkg/front_end/parser_testcases/record/record_type_01.dart.expect
+++ b/pkg/front_end/parser_testcases/record/record_type_01.dart.expect
@@ -444,7 +444,7 @@
           handleType(int, null)
           beginFunctionName(async)
             handleIdentifier(async, localFunctionDeclaration)
-          endFunctionName(int, ()
+          endFunctionName(int, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(int)
             endMetadataStar(0)
@@ -502,7 +502,7 @@
           endRecordType((, null, 2, false)
           beginFunctionName(async)
             handleIdentifier(async, localFunctionDeclaration)
-          endFunctionName((, ()
+          endFunctionName((, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(int)
             endMetadataStar(0)
@@ -560,7 +560,7 @@
           endRecordType((, null, 2, false)
           beginFunctionName(async)
             handleIdentifier(async, localFunctionDeclaration)
-          endFunctionName((, ()
+          endFunctionName((, (, false)
           beginFormalParameters((, MemberKind.Local)
             beginMetadataStar(int)
             endMetadataStar(0)
diff --git a/pkg/front_end/parser_testcases/record/record_type_01.dart.intertwined.expect b/pkg/front_end/parser_testcases/record/record_type_01.dart.intertwined.expect
index fd46786..459c1bc 100644
--- a/pkg/front_end/parser_testcases/record/record_type_01.dart.intertwined.expect
+++ b/pkg/front_end/parser_testcases/record/record_type_01.dart.intertwined.expect
@@ -886,7 +886,7 @@
                   ensureIdentifier(int, localFunctionDeclaration)
                     inPlainSync()
                     listener: handleIdentifier(async, localFunctionDeclaration)
-                  listener: endFunctionName(int, ()
+                  listener: endFunctionName(int, (, false)
                   parseFormalParametersRequiredOpt(async, MemberKind.Local)
                     parseFormalParametersRest((, MemberKind.Local)
                       listener: beginFormalParameters((, MemberKind.Local)
@@ -999,7 +999,7 @@
                     ensureIdentifier(), localFunctionDeclaration)
                       inPlainSync()
                       listener: handleIdentifier(async, localFunctionDeclaration)
-                    listener: endFunctionName((, ()
+                    listener: endFunctionName((, (, false)
                     parseFormalParametersRequiredOpt(async, MemberKind.Local)
                       parseFormalParametersRest((, MemberKind.Local)
                         listener: beginFormalParameters((, MemberKind.Local)
@@ -1112,7 +1112,7 @@
                     ensureIdentifier(), localFunctionDeclaration)
                       inPlainSync()
                       listener: handleIdentifier(async, localFunctionDeclaration)
-                    listener: endFunctionName((, ()
+                    listener: endFunctionName((, (, false)
                     parseFormalParametersRequiredOpt(async, MemberKind.Local)
                       parseFormalParametersRest((, MemberKind.Local)
                         listener: beginFormalParameters((, MemberKind.Local)
diff --git a/pkg/front_end/test/coverage_suite_expected.dart b/pkg/front_end/test/coverage_suite_expected.dart
index da6075f..6f0f885 100644
--- a/pkg/front_end/test/coverage_suite_expected.dart
+++ b/pkg/front_end/test/coverage_suite_expected.dart
@@ -175,7 +175,7 @@
   ),
   // 100.0%.
   "package:front_end/src/base/local_scope.dart": (
-    hitCount: 60,
+    hitCount: 59,
     missCount: 0,
   ),
   // 100.0%.
@@ -655,7 +655,7 @@
   ),
   // 100.0%.
   "package:front_end/src/kernel/body_builder.dart": (
-    hitCount: 7247,
+    hitCount: 7243,
     missCount: 0,
   ),
   // 100.0%.
@@ -1001,7 +1001,7 @@
   ),
   // 100.0%.
   "package:front_end/src/source/type_parameter_scope_builder.dart": (
-    hitCount: 1720,
+    hitCount: 1760,
     missCount: 0,
   ),
   // 100.0%.
@@ -1036,7 +1036,7 @@
   ),
   // 100.0%.
   "package:front_end/src/type_inference/inference_visitor.dart": (
-    hitCount: 8291,
+    hitCount: 8312,
     missCount: 0,
   ),
   // 100.0%.
diff --git a/pkg/front_end/test/parser_test_listener.dart b/pkg/front_end/test/parser_test_listener.dart
index bc8e248..fbbb294 100644
--- a/pkg/front_end/test/parser_test_listener.dart
+++ b/pkg/front_end/test/parser_test_listener.dart
@@ -1151,11 +1151,13 @@
   }
 
   @override
-  void endFunctionName(Token beginToken, Token token) {
+  void endFunctionName(
+      Token beginToken, Token token, bool isFunctionExpression) {
     indent--;
     seen(beginToken);
     seen(token);
-    doPrint('endFunctionName(' '$beginToken, ' '$token)');
+    doPrint(
+        'endFunctionName(' '$beginToken, ' '$token, ' '$isFunctionExpression)');
   }
 
   @override
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 590c4b1..30957b0 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -1308,6 +1308,7 @@
 pn
 pointed
 pointwise
+pollute
 polluted
 polymorphism
 pool
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart b/pkg/front_end/testcases/general/duplicate_local_function.dart
new file mode 100644
index 0000000..d409e89
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, 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.
+
+void test() {
+  void local() {}
+  void local() {}
+}
\ No newline at end of file
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.expect b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.expect
new file mode 100644
index 0000000..ded6f82
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.expect
@@ -0,0 +1,22 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/duplicate_local_function.dart:7:8: Error: 'local' is already declared in this scope.
+//   void local() {}
+//        ^^^^^
+// pkg/front_end/testcases/general/duplicate_local_function.dart:6:8: Context: Previous declaration of 'local'.
+//   void local() {}
+//        ^^^^^
+//
+import self as self;
+
+static method test() → void {
+  function local() → void {}
+  {
+    invalid-expression "pkg/front_end/testcases/general/duplicate_local_function.dart:7:8: Error: 'local' is already declared in this scope.
+  void local() {}
+       ^^^^^";
+    function local() → void {}
+  }
+}
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.modular.expect b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.modular.expect
new file mode 100644
index 0000000..ded6f82
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.modular.expect
@@ -0,0 +1,22 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/duplicate_local_function.dart:7:8: Error: 'local' is already declared in this scope.
+//   void local() {}
+//        ^^^^^
+// pkg/front_end/testcases/general/duplicate_local_function.dart:6:8: Context: Previous declaration of 'local'.
+//   void local() {}
+//        ^^^^^
+//
+import self as self;
+
+static method test() → void {
+  function local() → void {}
+  {
+    invalid-expression "pkg/front_end/testcases/general/duplicate_local_function.dart:7:8: Error: 'local' is already declared in this scope.
+  void local() {}
+       ^^^^^";
+    function local() → void {}
+  }
+}
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.outline.expect b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.outline.expect
new file mode 100644
index 0000000..643a05a
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.outline.expect
@@ -0,0 +1,5 @@
+library;
+import self as self;
+
+static method test() → void
+  ;
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.transformed.expect b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.transformed.expect
new file mode 100644
index 0000000..ded6f82
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart.strong.transformed.expect
@@ -0,0 +1,22 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/duplicate_local_function.dart:7:8: Error: 'local' is already declared in this scope.
+//   void local() {}
+//        ^^^^^
+// pkg/front_end/testcases/general/duplicate_local_function.dart:6:8: Context: Previous declaration of 'local'.
+//   void local() {}
+//        ^^^^^
+//
+import self as self;
+
+static method test() → void {
+  function local() → void {}
+  {
+    invalid-expression "pkg/front_end/testcases/general/duplicate_local_function.dart:7:8: Error: 'local' is already declared in this scope.
+  void local() {}
+       ^^^^^";
+    function local() → void {}
+  }
+}
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart.textual_outline.expect b/pkg/front_end/testcases/general/duplicate_local_function.dart.textual_outline.expect
new file mode 100644
index 0000000..7da700f
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart.textual_outline.expect
@@ -0,0 +1 @@
+void test() {}
diff --git a/pkg/front_end/testcases/general/duplicate_local_function.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/duplicate_local_function.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..7da700f
--- /dev/null
+++ b/pkg/front_end/testcases/general/duplicate_local_function.dart.textual_outline_modelled.expect
@@ -0,0 +1 @@
+void test() {}
diff --git a/pkg/front_end/testcases/general/named_function_scope.dart.strong.expect b/pkg/front_end/testcases/general/named_function_scope.dart.strong.expect
index 338b117..b92fa5d 100644
--- a/pkg/front_end/testcases/general/named_function_scope.dart.strong.expect
+++ b/pkg/front_end/testcases/general/named_function_scope.dart.strong.expect
@@ -42,13 +42,6 @@
 //     var x = T<T>() {};
 //             ^
 //
-// pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: 'T' is already declared in this scope.
-//     var x = T<T>() {};
-//             ^
-// pkg/front_end/testcases/general/named_function_scope.dart:52:15: Context: Previous declaration of 'T'.
-//     var x = T<T>() {};
-//               ^
-//
 // pkg/front_end/testcases/general/named_function_scope.dart:55:5: Error: Local variable 'T' can't be referenced before it is declared.
 //     T t;
 //     ^
@@ -155,12 +148,7 @@
   }
   {
     invalid-type x = block {
-      {
-        invalid-expression "pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: 'T' is already declared in this scope.
-    var x = T<T>() {};
-            ^";
-        function T<T extends core::Object? = dynamic>() → Null {}
-      }
+      function T<T extends core::Object? = dynamic>() → Null {}
     } =>invalid-expression "pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: A function expression can't have a name.
     var x = T<T>() {};
             ^";
diff --git a/pkg/front_end/testcases/general/named_function_scope.dart.strong.modular.expect b/pkg/front_end/testcases/general/named_function_scope.dart.strong.modular.expect
index 338b117..b92fa5d 100644
--- a/pkg/front_end/testcases/general/named_function_scope.dart.strong.modular.expect
+++ b/pkg/front_end/testcases/general/named_function_scope.dart.strong.modular.expect
@@ -42,13 +42,6 @@
 //     var x = T<T>() {};
 //             ^
 //
-// pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: 'T' is already declared in this scope.
-//     var x = T<T>() {};
-//             ^
-// pkg/front_end/testcases/general/named_function_scope.dart:52:15: Context: Previous declaration of 'T'.
-//     var x = T<T>() {};
-//               ^
-//
 // pkg/front_end/testcases/general/named_function_scope.dart:55:5: Error: Local variable 'T' can't be referenced before it is declared.
 //     T t;
 //     ^
@@ -155,12 +148,7 @@
   }
   {
     invalid-type x = block {
-      {
-        invalid-expression "pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: 'T' is already declared in this scope.
-    var x = T<T>() {};
-            ^";
-        function T<T extends core::Object? = dynamic>() → Null {}
-      }
+      function T<T extends core::Object? = dynamic>() → Null {}
     } =>invalid-expression "pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: A function expression can't have a name.
     var x = T<T>() {};
             ^";
diff --git a/pkg/front_end/testcases/general/named_function_scope.dart.strong.transformed.expect b/pkg/front_end/testcases/general/named_function_scope.dart.strong.transformed.expect
index 338b117..b92fa5d 100644
--- a/pkg/front_end/testcases/general/named_function_scope.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/general/named_function_scope.dart.strong.transformed.expect
@@ -42,13 +42,6 @@
 //     var x = T<T>() {};
 //             ^
 //
-// pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: 'T' is already declared in this scope.
-//     var x = T<T>() {};
-//             ^
-// pkg/front_end/testcases/general/named_function_scope.dart:52:15: Context: Previous declaration of 'T'.
-//     var x = T<T>() {};
-//               ^
-//
 // pkg/front_end/testcases/general/named_function_scope.dart:55:5: Error: Local variable 'T' can't be referenced before it is declared.
 //     T t;
 //     ^
@@ -155,12 +148,7 @@
   }
   {
     invalid-type x = block {
-      {
-        invalid-expression "pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: 'T' is already declared in this scope.
-    var x = T<T>() {};
-            ^";
-        function T<T extends core::Object? = dynamic>() → Null {}
-      }
+      function T<T extends core::Object? = dynamic>() → Null {}
     } =>invalid-expression "pkg/front_end/testcases/general/named_function_scope.dart:52:13: Error: A function expression can't have a name.
     var x = T<T>() {};
             ^";
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart b/pkg/front_end/testcases/general/recursive_named_function_expression.dart
new file mode 100644
index 0000000..7f52f75
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2025, 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.
+
+void test() {
+  var f = void foo() {
+    foo();
+  };
+}
\ No newline at end of file
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.expect b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.expect
new file mode 100644
index 0000000..0ab42b1
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.expect
@@ -0,0 +1,19 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/recursive_named_function_expression.dart:6:16: Error: A function expression can't have a name.
+//   var f = void foo() {
+//                ^^^
+//
+import self as self;
+
+static method test() → void {
+  invalid-type f = block {
+    function foo() → void {
+      foo(){() → void};
+    }
+  } =>invalid-expression "pkg/front_end/testcases/general/recursive_named_function_expression.dart:6:11: Error: A function expression can't have a name.
+  var f = void foo() {
+          ^";
+}
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.modular.expect b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.modular.expect
new file mode 100644
index 0000000..0ab42b1
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.modular.expect
@@ -0,0 +1,19 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/recursive_named_function_expression.dart:6:16: Error: A function expression can't have a name.
+//   var f = void foo() {
+//                ^^^
+//
+import self as self;
+
+static method test() → void {
+  invalid-type f = block {
+    function foo() → void {
+      foo(){() → void};
+    }
+  } =>invalid-expression "pkg/front_end/testcases/general/recursive_named_function_expression.dart:6:11: Error: A function expression can't have a name.
+  var f = void foo() {
+          ^";
+}
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.outline.expect b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.outline.expect
new file mode 100644
index 0000000..643a05a
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.outline.expect
@@ -0,0 +1,5 @@
+library;
+import self as self;
+
+static method test() → void
+  ;
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.transformed.expect b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.transformed.expect
new file mode 100644
index 0000000..0ab42b1
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.strong.transformed.expect
@@ -0,0 +1,19 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/recursive_named_function_expression.dart:6:16: Error: A function expression can't have a name.
+//   var f = void foo() {
+//                ^^^
+//
+import self as self;
+
+static method test() → void {
+  invalid-type f = block {
+    function foo() → void {
+      foo(){() → void};
+    }
+  } =>invalid-expression "pkg/front_end/testcases/general/recursive_named_function_expression.dart:6:11: Error: A function expression can't have a name.
+  var f = void foo() {
+          ^";
+}
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart.textual_outline.expect b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.textual_outline.expect
new file mode 100644
index 0000000..7da700f
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.textual_outline.expect
@@ -0,0 +1 @@
+void test() {}
diff --git a/pkg/front_end/testcases/general/recursive_named_function_expression.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..7da700f
--- /dev/null
+++ b/pkg/front_end/testcases/general/recursive_named_function_expression.dart.textual_outline_modelled.expect
@@ -0,0 +1 @@
+void test() {}