[cfe] Move body builder helpers to their own part

This helps navigate the already fairly long body_builder.dart file.

Change-Id: I091a8c63ed95893a37e21c863fd1a8b45137cc1d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448200
Reviewed-by: Jens Johansen <jensj@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/kernel/body_builder.dart b/pkg/front_end/lib/src/kernel/body_builder.dart
index 654aa48..764c1f6 100644
--- a/pkg/front_end/lib/src/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/kernel/body_builder.dart
@@ -133,14 +133,7 @@
 import 'type_algorithms.dart' show calculateBounds;
 import 'utils.dart';
 
-// TODO(ahe): Remove this and ensure all nodes have a location.
-const int noLocation = TreeNode.noOffset;
-
-enum JumpTargetKind {
-  Break,
-  Continue,
-  Goto, // Continue label in switch.
-}
+part 'body_builder_helpers.dart';
 
 class BodyBuilder extends StackListenerImpl
     implements ExpressionGeneratorHelper {
@@ -12315,558 +12308,3 @@
     push(dotShorthand);
   }
 }
-
-class Operator {
-  final Token token;
-
-  String get name => token.stringValue!;
-
-  final int charOffset;
-
-  Operator(this.token, this.charOffset);
-
-  @override
-  String toString() => "operator($name)";
-}
-
-class JumpTarget {
-  final List<Statement> users = <Statement>[];
-
-  final JumpTargetKind kind;
-
-  final int functionNestingLevel;
-
-  final Uri fileUri;
-
-  final int charOffset;
-
-  JumpTarget(
-    this.kind,
-    this.functionNestingLevel,
-    this.fileUri,
-    this.charOffset,
-  );
-
-  bool get isBreakTarget => kind == JumpTargetKind.Break;
-
-  bool get isContinueTarget => kind == JumpTargetKind.Continue;
-
-  bool get isGotoTarget => kind == JumpTargetKind.Goto;
-
-  bool get hasUsers => users.isNotEmpty;
-
-  void addBreak(Statement statement) {
-    assert(isBreakTarget);
-    users.add(statement);
-  }
-
-  void addContinue(Statement statement) {
-    assert(isContinueTarget);
-    users.add(statement);
-  }
-
-  void addGoto(Statement statement) {
-    assert(isGotoTarget);
-    users.add(statement);
-  }
-
-  void resolveBreaks(
-    Forest forest,
-    LabeledStatement target,
-    Statement targetStatement,
-  ) {
-    assert(isBreakTarget);
-    for (Statement user in users) {
-      BreakStatementImpl breakStatement = user as BreakStatementImpl;
-      breakStatement.target = target;
-      breakStatement.targetStatement = targetStatement;
-    }
-    users.clear();
-  }
-
-  List<BreakStatementImpl>? resolveContinues(
-    Forest forest,
-    LabeledStatement target,
-  ) {
-    assert(isContinueTarget);
-    List<BreakStatementImpl> statements = <BreakStatementImpl>[];
-    for (Statement user in users) {
-      BreakStatementImpl breakStatement = user as BreakStatementImpl;
-      breakStatement.target = target;
-      statements.add(breakStatement);
-    }
-    users.clear();
-    return statements;
-  }
-
-  void resolveGotos(Forest forest, SwitchCase target) {
-    assert(isGotoTarget);
-    for (Statement user in users) {
-      ContinueSwitchStatement continueSwitchStatement =
-          user as ContinueSwitchStatement;
-      continueSwitchStatement.target = target;
-    }
-    users.clear();
-  }
-}
-
-class LabelTarget implements JumpTarget {
-  final JumpTarget breakTarget;
-
-  final JumpTarget continueTarget;
-
-  @override
-  final int functionNestingLevel;
-
-  @override
-  final Uri fileUri;
-
-  @override
-  final int charOffset;
-
-  LabelTarget(this.functionNestingLevel, this.fileUri, this.charOffset)
-    : breakTarget = new JumpTarget(
-        JumpTargetKind.Break,
-        functionNestingLevel,
-        fileUri,
-        charOffset,
-      ),
-      continueTarget = new JumpTarget(
-        JumpTargetKind.Continue,
-        functionNestingLevel,
-        fileUri,
-        charOffset,
-      );
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  bool get hasUsers => breakTarget.hasUsers || continueTarget.hasUsers;
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  List<Statement> get users => unsupported("users", charOffset, fileUri);
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  JumpTargetKind get kind => unsupported("kind", charOffset, fileUri);
-
-  @override
-  bool get isBreakTarget => true;
-
-  @override
-  bool get isContinueTarget => true;
-
-  @override
-  bool get isGotoTarget => false;
-
-  @override
-  void addBreak(Statement statement) {
-    breakTarget.addBreak(statement);
-  }
-
-  @override
-  void addContinue(Statement statement) {
-    continueTarget.addContinue(statement);
-  }
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  void addGoto(Statement statement) {
-    unsupported("addGoto", charOffset, fileUri);
-  }
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  void resolveBreaks(
-    Forest forest,
-    LabeledStatement target,
-    Statement targetStatement,
-  ) {
-    breakTarget.resolveBreaks(forest, target, targetStatement);
-  }
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  List<BreakStatementImpl>? resolveContinues(
-    Forest forest,
-    LabeledStatement target,
-  ) {
-    return continueTarget.resolveContinues(forest, target);
-  }
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  void resolveGotos(Forest forest, SwitchCase target) {
-    unsupported("resolveGotos", charOffset, fileUri);
-  }
-}
-
-class FunctionTypeParameters {
-  final List<ParameterBuilder>? parameters;
-  final int charOffset;
-  final int length;
-  final Uri uri;
-
-  FunctionTypeParameters(
-    this.parameters,
-    this.charOffset,
-    this.length,
-    this.uri,
-  ) {
-    if (parameters?.isEmpty ?? false) {
-      throw "Empty parameters should be null";
-    }
-  }
-
-  TypeBuilder toFunctionType(
-    TypeBuilder returnType,
-    NullabilityBuilder nullabilityBuilder, {
-    List<StructuralParameterBuilder>? structuralVariableBuilders,
-    required bool hasFunctionFormalParameterSyntax,
-  }) {
-    return new FunctionTypeBuilderImpl(
-      returnType,
-      structuralVariableBuilders,
-      parameters,
-      nullabilityBuilder,
-      uri,
-      charOffset,
-      hasFunctionFormalParameterSyntax: hasFunctionFormalParameterSyntax,
-    );
-  }
-
-  @override
-  String toString() {
-    return "FormalParameters($parameters, $charOffset, $uri)";
-  }
-}
-
-class FormalParameters {
-  final List<FormalParameterBuilder>? parameters;
-  final int charOffset;
-  final int length;
-  final Uri uri;
-
-  FormalParameters(this.parameters, this.charOffset, this.length, this.uri) {
-    if (parameters?.isEmpty ?? false) {
-      throw "Empty parameters should be null";
-    }
-  }
-
-  FunctionNode buildFunctionNode(
-    SourceLibraryBuilder library,
-    TypeBuilder? returnTypeBuilder,
-    List<NominalParameterBuilder>? typeParameterBuilders,
-    AsyncMarker asyncModifier,
-    Statement body,
-    int fileEndOffset,
-  ) {
-    DartType returnType =
-        returnTypeBuilder?.build(library, TypeUse.returnType) ??
-        const DynamicType();
-    int requiredParameterCount = 0;
-    List<VariableDeclaration> positionalParameters = <VariableDeclaration>[];
-    List<VariableDeclaration> namedParameters = <VariableDeclaration>[];
-    if (parameters != null) {
-      for (FormalParameterBuilder formal in parameters!) {
-        VariableDeclaration parameter = formal.build(library);
-        if (formal.isPositional) {
-          positionalParameters.add(parameter);
-          if (formal.isRequiredPositional) requiredParameterCount++;
-        } else if (formal.isNamed) {
-          namedParameters.add(parameter);
-        }
-      }
-      namedParameters.sort((VariableDeclaration a, VariableDeclaration b) {
-        return a.name!.compareTo(b.name!);
-      });
-    }
-
-    List<TypeParameter>? typeParameters;
-    if (typeParameterBuilders != null) {
-      typeParameters = <TypeParameter>[];
-      for (NominalParameterBuilder t in typeParameterBuilders) {
-        typeParameters.add(t.parameter);
-        // Build the bound to detect cycles in typedefs.
-        t.bound?.build(library, TypeUse.typeParameterBound);
-      }
-    }
-    return new FunctionNode(
-        body,
-        typeParameters: typeParameters,
-        positionalParameters: positionalParameters,
-        namedParameters: namedParameters,
-        requiredParameterCount: requiredParameterCount,
-        returnType: returnType,
-        asyncMarker: asyncModifier,
-      )
-      ..fileOffset = charOffset
-      ..fileEndOffset = fileEndOffset;
-  }
-
-  LocalScope computeFormalParameterScope(
-    LocalScope parent,
-    ExpressionGeneratorHelper helper, {
-    bool wildcardVariablesEnabled = false,
-  }) {
-    if (parameters == null) return parent;
-    assert(parameters!.isNotEmpty);
-    Map<String, VariableBuilder> local = {};
-
-    for (FormalParameterBuilder parameter in parameters!) {
-      // Avoid having wildcard parameters in scope.
-      if (wildcardVariablesEnabled && parameter.isWildcard) continue;
-      Builder? existing = local[parameter.name];
-      if (existing != null) {
-        helper.reportDuplicatedDeclaration(
-          existing,
-          parameter.name,
-          parameter.fileOffset,
-        );
-      } else {
-        local[parameter.name] = parameter;
-      }
-    }
-    return parent.createNestedFixedScope(
-      debugName: "formals",
-      kind: ScopeKind.formals,
-      local: local,
-    );
-  }
-
-  @override
-  String toString() {
-    return "FormalParameters($parameters, $charOffset, $uri)";
-  }
-}
-
-/// Returns a block like this:
-///
-///     {
-///       statement;
-///       body;
-///     }
-///
-/// If [body] is a [Block], it's returned with [statement] prepended to it.
-Block combineStatements(Statement statement, Statement body) {
-  if (body is Block) {
-    if (statement is Block) {
-      body.statements.insertAll(0, statement.statements);
-      setParents(statement.statements, body);
-    } else {
-      body.statements.insert(0, statement);
-      statement.parent = body;
-    }
-    return body;
-  } else {
-    return new Block(<Statement>[
-      if (statement is Block) ...statement.statements else statement,
-      body,
-    ])..fileOffset = statement.fileOffset;
-  }
-}
-
-/// DartDocTest(
-///   debugName("myClassName", "myName"),
-///   "myClassName.myName"
-/// )
-/// DartDocTest(
-///   debugName("myClassName", ""),
-///   "myClassName"
-/// )
-/// DartDocTest(
-///   debugName("", ""),
-///   ""
-/// )
-String debugName(String className, String name) {
-  return name.isEmpty ? className : "$className.$name";
-}
-
-/// A data holder used to hold the information about a label that is pushed on
-/// the stack.
-class Label {
-  String name;
-  int charOffset;
-
-  Label(this.name, this.charOffset);
-
-  @override
-  String toString() => "label($name)";
-}
-
-class ForInElements {
-  VariableDeclaration? explicitVariableDeclaration;
-  VariableDeclaration? syntheticVariableDeclaration;
-  Expression? syntheticAssignment;
-  Expression? expressionProblem;
-  Statement? expressionEffects;
-
-  VariableDeclaration get variable =>
-      (explicitVariableDeclaration ?? syntheticVariableDeclaration)!;
-}
-
-class _BodyBuilderCloner extends CloneVisitorNotMembers {
-  final BodyBuilder bodyBuilder;
-
-  _BodyBuilderCloner(this.bodyBuilder);
-
-  @override
-  // Coverage-ignore(suite): Not run.
-  TreeNode visitStaticInvocation(StaticInvocation node) {
-    if (node is FactoryConstructorInvocation) {
-      FactoryConstructorInvocation result = new FactoryConstructorInvocation(
-        node.target,
-        clone(node.arguments),
-        isConst: node.isConst,
-      )..hasBeenInferred = node.hasBeenInferred;
-      return result;
-    } else if (node is TypeAliasedFactoryInvocation) {
-      TypeAliasedFactoryInvocation result = new TypeAliasedFactoryInvocation(
-        node.typeAliasBuilder,
-        node.target,
-        clone(node.arguments),
-        isConst: node.isConst,
-      )..hasBeenInferred = node.hasBeenInferred;
-      return result;
-    }
-    return super.visitStaticInvocation(node);
-  }
-
-  @override
-  TreeNode visitConstructorInvocation(ConstructorInvocation node) {
-    if (node is TypeAliasedConstructorInvocation) {
-      // Coverage-ignore-block(suite): Not run.
-      TypeAliasedConstructorInvocation result =
-          new TypeAliasedConstructorInvocation(
-            node.typeAliasBuilder,
-            node.target,
-            clone(node.arguments),
-            isConst: node.isConst,
-          )..hasBeenInferred = node.hasBeenInferred;
-      return result;
-    }
-    return super.visitConstructorInvocation(node);
-  }
-
-  @override
-  TreeNode visitArguments(Arguments node) {
-    if (node is ArgumentsImpl) {
-      return ArgumentsImpl.clone(
-        node,
-        node.positional.map(clone).toList(),
-        node.named.map(clone).toList(),
-        node.types.map(visitType).toList(),
-      );
-    }
-    return super.visitArguments(node);
-  }
-}
-
-// Coverage-ignore(suite): Not run.
-/// Returns `true` if [node] is not part of its parent member.
-///
-/// This computation is costly and should only be used in assertions to verify
-/// that [node] has been removed from the AST.
-bool isOrphaned(TreeNode node) {
-  TreeNode? parent = node;
-  Member? member;
-  while (parent != null) {
-    if (parent is Member) {
-      member = parent;
-      break;
-    }
-    parent = parent.parent;
-  }
-  if (member == null) {
-    return true;
-  }
-  _FindChildVisitor visitor = new _FindChildVisitor(node);
-  member.accept(visitor);
-  return !visitor.foundNode;
-}
-
-// Coverage-ignore(suite): Not run.
-class _FindChildVisitor extends VisitorDefault<void> with VisitorVoidMixin {
-  final TreeNode soughtNode;
-  bool foundNode = false;
-
-  _FindChildVisitor(this.soughtNode);
-
-  @override
-  void defaultNode(Node node) {
-    if (!foundNode) {
-      if (identical(node, soughtNode)) {
-        foundNode = true;
-      } else {
-        node.visitChildren(this);
-      }
-    }
-  }
-}
-
-class Condition {
-  final Expression expression;
-  final PatternGuard? patternGuard;
-
-  Condition(this.expression, [this.patternGuard]);
-
-  @override
-  String toString() =>
-      'Condition($expression'
-      '${patternGuard != null ? ',$patternGuard' : ''})';
-}
-
-final ExpressionOrPatternGuardCase dummyExpressionOrPatternGuardCase =
-    new ExpressionOrPatternGuardCase.expression(
-      TreeNode.noOffset,
-      dummyExpression,
-    );
-
-class ExpressionOrPatternGuardCase {
-  final int caseOffset;
-  final Expression? expression;
-  final PatternGuard? patternGuard;
-
-  ExpressionOrPatternGuardCase.expression(
-    this.caseOffset,
-    Expression this.expression,
-  ) : patternGuard = null;
-
-  ExpressionOrPatternGuardCase.patternGuard(
-    this.caseOffset,
-    PatternGuard this.patternGuard,
-  ) : expression = null;
-}
-
-class RedirectionTarget {
-  final Member target;
-  final List<DartType> typeArguments;
-
-  RedirectionTarget(this.target, this.typeArguments);
-}
-
-extension on MemberKind {
-  bool get isFunctionType {
-    switch (this) {
-      case MemberKind.FunctionTypeAlias:
-      case MemberKind.FunctionTypedParameter:
-      case MemberKind.GeneralizedFunctionType:
-        return true;
-      case MemberKind.Catch:
-      case MemberKind.Factory:
-      case MemberKind.Local:
-      case MemberKind.NonStaticMethod:
-      case MemberKind.StaticMethod:
-      case MemberKind.TopLevelMethod:
-      case MemberKind.ExtensionNonStaticMethod:
-      case MemberKind.ExtensionStaticMethod:
-      case MemberKind.ExtensionTypeNonStaticMethod:
-      case MemberKind.ExtensionTypeStaticMethod:
-      case MemberKind.NonStaticField:
-      case MemberKind.StaticField:
-      case MemberKind.TopLevelField:
-      case MemberKind.PrimaryConstructor:
-        return false;
-    }
-  }
-}
diff --git a/pkg/front_end/lib/src/kernel/body_builder_helpers.dart b/pkg/front_end/lib/src/kernel/body_builder_helpers.dart
new file mode 100644
index 0000000..3e7d4fc
--- /dev/null
+++ b/pkg/front_end/lib/src/kernel/body_builder_helpers.dart
@@ -0,0 +1,569 @@
+// 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.
+
+part of 'body_builder.dart';
+
+// TODO(ahe): Remove this and ensure all nodes have a location.
+const int noLocation = TreeNode.noOffset;
+
+enum JumpTargetKind {
+  Break,
+  Continue,
+  Goto, // Continue label in switch.
+}
+
+class Operator {
+  final Token token;
+
+  String get name => token.stringValue!;
+
+  final int charOffset;
+
+  Operator(this.token, this.charOffset);
+
+  @override
+  String toString() => "operator($name)";
+}
+
+class JumpTarget {
+  final List<Statement> users = <Statement>[];
+
+  final JumpTargetKind kind;
+
+  final int functionNestingLevel;
+
+  final Uri fileUri;
+
+  final int charOffset;
+
+  JumpTarget(
+    this.kind,
+    this.functionNestingLevel,
+    this.fileUri,
+    this.charOffset,
+  );
+
+  bool get isBreakTarget => kind == JumpTargetKind.Break;
+
+  bool get isContinueTarget => kind == JumpTargetKind.Continue;
+
+  bool get isGotoTarget => kind == JumpTargetKind.Goto;
+
+  bool get hasUsers => users.isNotEmpty;
+
+  void addBreak(Statement statement) {
+    assert(isBreakTarget);
+    users.add(statement);
+  }
+
+  void addContinue(Statement statement) {
+    assert(isContinueTarget);
+    users.add(statement);
+  }
+
+  void addGoto(Statement statement) {
+    assert(isGotoTarget);
+    users.add(statement);
+  }
+
+  void resolveBreaks(
+    Forest forest,
+    LabeledStatement target,
+    Statement targetStatement,
+  ) {
+    assert(isBreakTarget);
+    for (Statement user in users) {
+      BreakStatementImpl breakStatement = user as BreakStatementImpl;
+      breakStatement.target = target;
+      breakStatement.targetStatement = targetStatement;
+    }
+    users.clear();
+  }
+
+  List<BreakStatementImpl>? resolveContinues(
+    Forest forest,
+    LabeledStatement target,
+  ) {
+    assert(isContinueTarget);
+    List<BreakStatementImpl> statements = <BreakStatementImpl>[];
+    for (Statement user in users) {
+      BreakStatementImpl breakStatement = user as BreakStatementImpl;
+      breakStatement.target = target;
+      statements.add(breakStatement);
+    }
+    users.clear();
+    return statements;
+  }
+
+  void resolveGotos(Forest forest, SwitchCase target) {
+    assert(isGotoTarget);
+    for (Statement user in users) {
+      ContinueSwitchStatement continueSwitchStatement =
+          user as ContinueSwitchStatement;
+      continueSwitchStatement.target = target;
+    }
+    users.clear();
+  }
+}
+
+class LabelTarget implements JumpTarget {
+  final JumpTarget breakTarget;
+
+  final JumpTarget continueTarget;
+
+  @override
+  final int functionNestingLevel;
+
+  @override
+  final Uri fileUri;
+
+  @override
+  final int charOffset;
+
+  LabelTarget(this.functionNestingLevel, this.fileUri, this.charOffset)
+    : breakTarget = new JumpTarget(
+        JumpTargetKind.Break,
+        functionNestingLevel,
+        fileUri,
+        charOffset,
+      ),
+      continueTarget = new JumpTarget(
+        JumpTargetKind.Continue,
+        functionNestingLevel,
+        fileUri,
+        charOffset,
+      );
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  bool get hasUsers => breakTarget.hasUsers || continueTarget.hasUsers;
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  List<Statement> get users => unsupported("users", charOffset, fileUri);
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  JumpTargetKind get kind => unsupported("kind", charOffset, fileUri);
+
+  @override
+  bool get isBreakTarget => true;
+
+  @override
+  bool get isContinueTarget => true;
+
+  @override
+  bool get isGotoTarget => false;
+
+  @override
+  void addBreak(Statement statement) {
+    breakTarget.addBreak(statement);
+  }
+
+  @override
+  void addContinue(Statement statement) {
+    continueTarget.addContinue(statement);
+  }
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  void addGoto(Statement statement) {
+    unsupported("addGoto", charOffset, fileUri);
+  }
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  void resolveBreaks(
+    Forest forest,
+    LabeledStatement target,
+    Statement targetStatement,
+  ) {
+    breakTarget.resolveBreaks(forest, target, targetStatement);
+  }
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  List<BreakStatementImpl>? resolveContinues(
+    Forest forest,
+    LabeledStatement target,
+  ) {
+    return continueTarget.resolveContinues(forest, target);
+  }
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  void resolveGotos(Forest forest, SwitchCase target) {
+    unsupported("resolveGotos", charOffset, fileUri);
+  }
+}
+
+class FunctionTypeParameters {
+  final List<ParameterBuilder>? parameters;
+  final int charOffset;
+  final int length;
+  final Uri uri;
+
+  FunctionTypeParameters(
+    this.parameters,
+    this.charOffset,
+    this.length,
+    this.uri,
+  ) {
+    if (parameters?.isEmpty ?? false) {
+      throw "Empty parameters should be null";
+    }
+  }
+
+  TypeBuilder toFunctionType(
+    TypeBuilder returnType,
+    NullabilityBuilder nullabilityBuilder, {
+    List<StructuralParameterBuilder>? structuralVariableBuilders,
+    required bool hasFunctionFormalParameterSyntax,
+  }) {
+    return new FunctionTypeBuilderImpl(
+      returnType,
+      structuralVariableBuilders,
+      parameters,
+      nullabilityBuilder,
+      uri,
+      charOffset,
+      hasFunctionFormalParameterSyntax: hasFunctionFormalParameterSyntax,
+    );
+  }
+
+  @override
+  String toString() {
+    return "FormalParameters($parameters, $charOffset, $uri)";
+  }
+}
+
+class FormalParameters {
+  final List<FormalParameterBuilder>? parameters;
+  final int charOffset;
+  final int length;
+  final Uri uri;
+
+  FormalParameters(this.parameters, this.charOffset, this.length, this.uri) {
+    if (parameters?.isEmpty ?? false) {
+      throw "Empty parameters should be null";
+    }
+  }
+
+  FunctionNode buildFunctionNode(
+    SourceLibraryBuilder library,
+    TypeBuilder? returnTypeBuilder,
+    List<NominalParameterBuilder>? typeParameterBuilders,
+    AsyncMarker asyncModifier,
+    Statement body,
+    int fileEndOffset,
+  ) {
+    DartType returnType =
+        returnTypeBuilder?.build(library, TypeUse.returnType) ??
+        const DynamicType();
+    int requiredParameterCount = 0;
+    List<VariableDeclaration> positionalParameters = <VariableDeclaration>[];
+    List<VariableDeclaration> namedParameters = <VariableDeclaration>[];
+    if (parameters != null) {
+      for (FormalParameterBuilder formal in parameters!) {
+        VariableDeclaration parameter = formal.build(library);
+        if (formal.isPositional) {
+          positionalParameters.add(parameter);
+          if (formal.isRequiredPositional) requiredParameterCount++;
+        } else if (formal.isNamed) {
+          namedParameters.add(parameter);
+        }
+      }
+      namedParameters.sort((VariableDeclaration a, VariableDeclaration b) {
+        return a.name!.compareTo(b.name!);
+      });
+    }
+
+    List<TypeParameter>? typeParameters;
+    if (typeParameterBuilders != null) {
+      typeParameters = <TypeParameter>[];
+      for (NominalParameterBuilder t in typeParameterBuilders) {
+        typeParameters.add(t.parameter);
+        // Build the bound to detect cycles in typedefs.
+        t.bound?.build(library, TypeUse.typeParameterBound);
+      }
+    }
+    return new FunctionNode(
+        body,
+        typeParameters: typeParameters,
+        positionalParameters: positionalParameters,
+        namedParameters: namedParameters,
+        requiredParameterCount: requiredParameterCount,
+        returnType: returnType,
+        asyncMarker: asyncModifier,
+      )
+      ..fileOffset = charOffset
+      ..fileEndOffset = fileEndOffset;
+  }
+
+  LocalScope computeFormalParameterScope(
+    LocalScope parent,
+    ExpressionGeneratorHelper helper, {
+    bool wildcardVariablesEnabled = false,
+  }) {
+    if (parameters == null) return parent;
+    assert(parameters!.isNotEmpty);
+    Map<String, VariableBuilder> local = {};
+
+    for (FormalParameterBuilder parameter in parameters!) {
+      // Avoid having wildcard parameters in scope.
+      if (wildcardVariablesEnabled && parameter.isWildcard) continue;
+      Builder? existing = local[parameter.name];
+      if (existing != null) {
+        helper.reportDuplicatedDeclaration(
+          existing,
+          parameter.name,
+          parameter.fileOffset,
+        );
+      } else {
+        local[parameter.name] = parameter;
+      }
+    }
+    return parent.createNestedFixedScope(
+      debugName: "formals",
+      kind: ScopeKind.formals,
+      local: local,
+    );
+  }
+
+  @override
+  String toString() {
+    return "FormalParameters($parameters, $charOffset, $uri)";
+  }
+}
+
+/// Returns a block like this:
+///
+///     {
+///       statement;
+///       body;
+///     }
+///
+/// If [body] is a [Block], it's returned with [statement] prepended to it.
+Block combineStatements(Statement statement, Statement body) {
+  if (body is Block) {
+    if (statement is Block) {
+      body.statements.insertAll(0, statement.statements);
+      setParents(statement.statements, body);
+    } else {
+      body.statements.insert(0, statement);
+      statement.parent = body;
+    }
+    return body;
+  } else {
+    return new Block(<Statement>[
+      if (statement is Block) ...statement.statements else statement,
+      body,
+    ])..fileOffset = statement.fileOffset;
+  }
+}
+
+/// DartDocTest(
+///   debugName("myClassName", "myName"),
+///   "myClassName.myName"
+/// )
+/// DartDocTest(
+///   debugName("myClassName", ""),
+///   "myClassName"
+/// )
+/// DartDocTest(
+///   debugName("", ""),
+///   ""
+/// )
+String debugName(String className, String name) {
+  return name.isEmpty ? className : "$className.$name";
+}
+
+/// A data holder used to hold the information about a label that is pushed on
+/// the stack.
+class Label {
+  String name;
+  int charOffset;
+
+  Label(this.name, this.charOffset);
+
+  @override
+  String toString() => "label($name)";
+}
+
+class ForInElements {
+  VariableDeclaration? explicitVariableDeclaration;
+  VariableDeclaration? syntheticVariableDeclaration;
+  Expression? syntheticAssignment;
+  Expression? expressionProblem;
+  Statement? expressionEffects;
+
+  VariableDeclaration get variable =>
+      (explicitVariableDeclaration ?? syntheticVariableDeclaration)!;
+}
+
+class _BodyBuilderCloner extends CloneVisitorNotMembers {
+  final BodyBuilder bodyBuilder;
+
+  _BodyBuilderCloner(this.bodyBuilder);
+
+  @override
+  // Coverage-ignore(suite): Not run.
+  TreeNode visitStaticInvocation(StaticInvocation node) {
+    if (node is FactoryConstructorInvocation) {
+      FactoryConstructorInvocation result = new FactoryConstructorInvocation(
+        node.target,
+        clone(node.arguments),
+        isConst: node.isConst,
+      )..hasBeenInferred = node.hasBeenInferred;
+      return result;
+    } else if (node is TypeAliasedFactoryInvocation) {
+      TypeAliasedFactoryInvocation result = new TypeAliasedFactoryInvocation(
+        node.typeAliasBuilder,
+        node.target,
+        clone(node.arguments),
+        isConst: node.isConst,
+      )..hasBeenInferred = node.hasBeenInferred;
+      return result;
+    }
+    return super.visitStaticInvocation(node);
+  }
+
+  @override
+  TreeNode visitConstructorInvocation(ConstructorInvocation node) {
+    if (node is TypeAliasedConstructorInvocation) {
+      // Coverage-ignore-block(suite): Not run.
+      TypeAliasedConstructorInvocation result =
+          new TypeAliasedConstructorInvocation(
+            node.typeAliasBuilder,
+            node.target,
+            clone(node.arguments),
+            isConst: node.isConst,
+          )..hasBeenInferred = node.hasBeenInferred;
+      return result;
+    }
+    return super.visitConstructorInvocation(node);
+  }
+
+  @override
+  TreeNode visitArguments(Arguments node) {
+    if (node is ArgumentsImpl) {
+      return ArgumentsImpl.clone(
+        node,
+        node.positional.map(clone).toList(),
+        node.named.map(clone).toList(),
+        node.types.map(visitType).toList(),
+      );
+    }
+    return super.visitArguments(node);
+  }
+}
+
+// Coverage-ignore(suite): Not run.
+/// Returns `true` if [node] is not part of its parent member.
+///
+/// This computation is costly and should only be used in assertions to verify
+/// that [node] has been removed from the AST.
+bool isOrphaned(TreeNode node) {
+  TreeNode? parent = node;
+  Member? member;
+  while (parent != null) {
+    if (parent is Member) {
+      member = parent;
+      break;
+    }
+    parent = parent.parent;
+  }
+  if (member == null) {
+    return true;
+  }
+  _FindChildVisitor visitor = new _FindChildVisitor(node);
+  member.accept(visitor);
+  return !visitor.foundNode;
+}
+
+// Coverage-ignore(suite): Not run.
+class _FindChildVisitor extends VisitorDefault<void> with VisitorVoidMixin {
+  final TreeNode soughtNode;
+  bool foundNode = false;
+
+  _FindChildVisitor(this.soughtNode);
+
+  @override
+  void defaultNode(Node node) {
+    if (!foundNode) {
+      if (identical(node, soughtNode)) {
+        foundNode = true;
+      } else {
+        node.visitChildren(this);
+      }
+    }
+  }
+}
+
+class Condition {
+  final Expression expression;
+  final PatternGuard? patternGuard;
+
+  Condition(this.expression, [this.patternGuard]);
+
+  @override
+  String toString() =>
+      'Condition($expression'
+      '${patternGuard != null ? ',$patternGuard' : ''})';
+}
+
+final ExpressionOrPatternGuardCase dummyExpressionOrPatternGuardCase =
+    new ExpressionOrPatternGuardCase.expression(
+      TreeNode.noOffset,
+      dummyExpression,
+    );
+
+class ExpressionOrPatternGuardCase {
+  final int caseOffset;
+  final Expression? expression;
+  final PatternGuard? patternGuard;
+
+  ExpressionOrPatternGuardCase.expression(
+    this.caseOffset,
+    Expression this.expression,
+  ) : patternGuard = null;
+
+  ExpressionOrPatternGuardCase.patternGuard(
+    this.caseOffset,
+    PatternGuard this.patternGuard,
+  ) : expression = null;
+}
+
+class RedirectionTarget {
+  final Member target;
+  final List<DartType> typeArguments;
+
+  RedirectionTarget(this.target, this.typeArguments);
+}
+
+extension on MemberKind {
+  bool get isFunctionType {
+    switch (this) {
+      case MemberKind.FunctionTypeAlias:
+      case MemberKind.FunctionTypedParameter:
+      case MemberKind.GeneralizedFunctionType:
+        return true;
+      case MemberKind.Catch:
+      case MemberKind.Factory:
+      case MemberKind.Local:
+      case MemberKind.NonStaticMethod:
+      case MemberKind.StaticMethod:
+      case MemberKind.TopLevelMethod:
+      case MemberKind.ExtensionNonStaticMethod:
+      case MemberKind.ExtensionStaticMethod:
+      case MemberKind.ExtensionTypeNonStaticMethod:
+      case MemberKind.ExtensionTypeStaticMethod:
+      case MemberKind.NonStaticField:
+      case MemberKind.StaticField:
+      case MemberKind.TopLevelField:
+      case MemberKind.PrimaryConstructor:
+        return false;
+    }
+  }
+}
diff --git a/pkg/front_end/test/dartdoctest_suite.status b/pkg/front_end/test/dartdoctest_suite.status
index 1bb32d5..24ef63d 100644
--- a/pkg/front_end/test/dartdoctest_suite.status
+++ b/pkg/front_end/test/dartdoctest_suite.status
@@ -2,3 +2,4 @@
 # 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.
 
+front_end/lib/src/kernel/body_builder_helpers: Crash # Issue 61438
\ No newline at end of file