// Copyright (c) 2019, 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.

// ignore_for_file: analyzer_use_new_elements

import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
    as shared;
import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/scope.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/scope.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_constraint_gatherer.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/resolver/ast_rewrite.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/named_type_resolver.dart';
import 'package:analyzer/src/dart/resolver/record_type_annotation_resolver.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/element_walker.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';

class ElementHolder {
  final ElementImpl _element;
  final List<TypeParameterElementImpl> _typeParameters = [];
  final List<ParameterElementImpl> _parameters = [];

  ElementHolder(this._element);

  List<ParameterElementImpl> get parameters {
    return _parameters.toFixedList();
  }

  List<TypeParameterElementImpl> get typeParameters {
    return _typeParameters.toFixedList();
  }

  void addParameter(ParameterElementImpl element) {
    _parameters.add(element);
  }

  void addTypeParameter(TypeParameterElementImpl element) {
    _typeParameters.add(element);
  }

  void enclose(ElementImpl element) {
    element.enclosingElement3 = _element;
  }
}

/// Recursively visit AST and perform following resolution tasks:
///
/// 1. Set existing top-level elements from [_elementWalker] to corresponding
///    nodes in AST.
/// 2. Create and set new elements for local declarations.
/// 3. Resolve all [NamedType]s - set elements and types.
/// 4. Resolve all [GenericFunctionType]s - set their types.
/// 5. Rewrite AST where resolution provides a more accurate understanding.
class ResolutionVisitor extends RecursiveAstVisitor<void> {
  final LibraryElementImpl _libraryElement;
  final TypeProviderImpl _typeProvider;
  final CompilationUnitElementImpl _unitElement;
  final ErrorReporter _errorReporter;
  final AstRewriter _astRewriter;
  final NamedTypeResolver _namedTypeResolver;
  final RecordTypeAnnotationResolver _recordTypeResolver;

  /// This index is incremented every time we visit a [LibraryDirective].
  /// There is just one [LibraryElement], so we can support only one node.
  int _libraryDirectiveIndex = 0;

  /// The provider of pre-built children elements from the element being
  /// visited. For example when we visit a method, its element is resynthesized
  /// from the summary, and we get resynthesized elements for type parameters
  /// and formal parameters to apply to corresponding AST nodes.
  ElementWalker? _elementWalker;

  /// The scope used to resolve identifiers.
  Scope _nameScope;

  /// The scope used to resolve labels for `break` and `continue` statements,
  /// or `null` if no labels have been defined in the current context.
  LabelScope? _labelScope;

  /// The container to add newly created elements that should be put into the
  /// enclosing element.
  ElementHolder _elementHolder;

  /// Data structure for tracking declared pattern variables.
  late final _VariableBinder _patternVariables = _VariableBinder(
    errors: _VariableBinderErrors(this),
    typeProvider: _typeProvider,
  );

  /// The set of required operations on types.
  final TypeSystemOperations typeSystemOperations;

  final TypeConstraintGenerationDataForTesting? dataForTesting;

  factory ResolutionVisitor({
    required CompilationUnitElementImpl unitElement,
    required AnalysisErrorListener errorListener,
    required Scope nameScope,
    required bool strictInference,
    required bool strictCasts,
    ElementWalker? elementWalker,
    required TypeConstraintGenerationDataForTesting? dataForTesting,
  }) {
    var libraryElement = unitElement.library;
    var typeProvider = libraryElement.typeProvider;
    var unitSource = unitElement.source;
    var errorReporter = ErrorReporter(errorListener, unitSource);

    var typeSystemOperations = TypeSystemOperations(
      unitElement.library.typeSystem,
      strictCasts: strictCasts,
    );

    var namedTypeResolver = NamedTypeResolver(
      libraryElement,
      unitElement,
      errorReporter,
      strictInference: strictInference,
      strictCasts: strictCasts,
      typeSystemOperations: typeSystemOperations,
    );

    var recordTypeResolver = RecordTypeAnnotationResolver(
      typeProvider: typeProvider,
      errorReporter: errorReporter,
      libraryElement: libraryElement,
    );

    return ResolutionVisitor._(
      libraryElement,
      typeProvider,
      unitElement,
      errorReporter,
      AstRewriter(errorReporter),
      namedTypeResolver,
      recordTypeResolver,
      nameScope,
      elementWalker,
      ElementHolder(unitElement),
      typeSystemOperations,
      dataForTesting,
    );
  }

  ResolutionVisitor._(
    this._libraryElement,
    this._typeProvider,
    this._unitElement,
    this._errorReporter,
    this._astRewriter,
    this._namedTypeResolver,
    this._recordTypeResolver,
    this._nameScope,
    this._elementWalker,
    this._elementHolder,
    this.typeSystemOperations,
    this.dataForTesting,
  );

  TypeImpl get _dynamicType => _typeProvider.dynamicType;

  @override
  void visitAnnotation(covariant AnnotationImpl node) {
    if (_elementWalker == null) {
      _createElementAnnotation(node);
    }
    _withElementWalker(null, () {
      super.visitAnnotation(node);
    });
  }

  @override
  void visitAssignedVariablePattern(
    covariant AssignedVariablePatternImpl node,
  ) {
    var name = node.name.lexeme;
    var element = _nameScope.lookup(name).getter;
    node.element = element;

    if (element == null) {
      _errorReporter.atToken(
        node.name,
        CompileTimeErrorCode.UNDEFINED_IDENTIFIER,
        arguments: [name],
      );
    } else if (!(element is LocalVariableElement ||
        element is ParameterElement)) {
      _errorReporter.atToken(
        node.name,
        CompileTimeErrorCode.PATTERN_ASSIGNMENT_NOT_LOCAL_VARIABLE,
      );
    }
  }

  @override
  void visitBlock(Block node) {
    var outerScope = _nameScope;
    try {
      _nameScope = LocalScope(_nameScope);

      var statements = node.statements;
      _buildLocalElements(statements);
      statements.accept(this);
    } finally {
      _nameScope = outerScope;
    }
  }

  @override
  void visitCatchClause(covariant CatchClauseImpl node) {
    var exceptionTypeNode = node.exceptionType;
    exceptionTypeNode?.accept(this);

    _withNameScope(() {
      var exceptionNode = node.exceptionParameter;
      if (exceptionNode != null) {
        var element = LocalVariableElementImpl(
          exceptionNode.name.lexeme,
          exceptionNode.name.offset,
        );
        _elementHolder.enclose(element);
        _define(element);

        exceptionNode.declaredElement = element;

        element.isFinal = true;
        if (exceptionTypeNode == null) {
          element.hasImplicitType = true;
          element.type = _typeProvider.objectType;
        } else {
          element.type = exceptionTypeNode.typeOrThrow;
        }

        element.setCodeRange(
          exceptionNode.name.offset,
          exceptionNode.name.length,
        );
      }

      var stackTraceNode = node.stackTraceParameter;
      if (stackTraceNode != null) {
        var element = LocalVariableElementImpl(
          stackTraceNode.name.lexeme,
          stackTraceNode.name.offset,
        );
        _elementHolder.enclose(element);
        _define(element);

        stackTraceNode.declaredElement = element;

        element.isFinal = true;
        element.type = _typeProvider.stackTraceType;

        element.setCodeRange(
          stackTraceNode.name.offset,
          stackTraceNode.name.length,
        );
      }

      node.body.accept(this);
    });
  }

  @override
  void visitClassDeclaration(covariant ClassDeclarationImpl node) {
    ClassElementImpl element = _elementWalker!.getClass();
    node.declaredElement = element;
    _namedTypeResolver.enclosingClass = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forClass(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);

        var extendsClause = node.extendsClause;
        var withClause = node.withClause;

        if (extendsClause != null) {
          _resolveType(
            declaration: node,
            clause: extendsClause,
            namedType: extendsClause.superclass,
          );
        }

        _resolveWithClause(
          declaration: node,
          clause: withClause,
        );
        _resolveImplementsClause(
          declaration: node,
          clause: node.implementsClause,
        );

        _defineElements(element.accessors);
        _defineElements(element.methods);
        node.members.accept(this);
      });
    });

    _namedTypeResolver.enclosingClass = null;
  }

  @override
  void visitClassTypeAlias(covariant ClassTypeAliasImpl node) {
    ClassElementImpl element = _elementWalker!.getClass();
    node.declaredElement = element;
    _namedTypeResolver.enclosingClass = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forClass(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);

        _resolveType(
          declaration: node,
          clause: null,
          namedType: node.superclass,
        );

        _resolveWithClause(
          declaration: node,
          clause: node.withClause,
        );
        _resolveImplementsClause(
          declaration: node,
          clause: node.implementsClause,
        );
      });
    });

    _namedTypeResolver.enclosingClass = null;
  }

  @override
  void visitConstructorDeclaration(ConstructorDeclaration node) {
    ConstructorElementImpl element = _elementWalker!.getConstructor();
    (node as ConstructorDeclarationImpl).declaredElement = element;
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementHolder(ElementHolder(element), () {
      _withElementWalker(null, () {
        _withNameScope(() {
          node.returnType.accept(this);

          _withElementWalker(
            ElementWalker.forExecutable(element),
            () {
              node.parameters.accept(this);
            },
          );
          _defineParameters(element.parameters);

          _resolveRedirectedConstructor(node);
          node.initializers.accept(this);
          node.body.accept(this);
        });
      });
    });
  }

  @override
  void visitDeclaredIdentifier(covariant DeclaredIdentifierImpl node) {
    var nameToken = node.name;
    var element = LocalVariableElementImpl(nameToken.lexeme, nameToken.offset);
    _elementHolder.enclose(element);
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    element.isConst = node.isConst;
    element.isFinal = node.isFinal;

    if (node.type == null) {
      element.hasImplicitType = true;
      element.type = _dynamicType;
    } else {
      node.type!.accept(this);
      element.type = node.type!.typeOrThrow;
    }

    _setCodeRange(element, node);
  }

  @override
  void visitDeclaredVariablePattern(
      covariant DeclaredVariablePatternImpl node) {
    node.type?.accept(this);

    var name = node.name.lexeme;
    var element = BindPatternVariableElementImpl(
      node,
      name,
      node.name.offset,
    );
    _patternVariables.add(name, element.element);
    _elementHolder.enclose(element);
    _define(element);
    element.hasImplicitType = node.type == null;
    element.type = node.type?.type ?? InvalidTypeImpl.instance;
    node.declaredElement = element;

    var patternContext = node.patternContext;
    if (patternContext is ForEachPartsWithPatternImpl) {
      element.isFinal = patternContext.finalKeyword != null;
    } else if (patternContext is PatternVariableDeclarationImpl) {
      element.isFinal = patternContext.finalKeyword != null;
    } else {
      element.isFinal = node.finalKeyword != null;
    }
  }

  @override
  void visitDefaultFormalParameter(covariant DefaultFormalParameterImpl node) {
    var normalParameter = node.parameter;
    var nameToken = normalParameter.name;

    ParameterElementImpl element;
    if (_elementWalker != null) {
      element = _elementWalker!.getParameter();
    } else {
      var name = nameToken?.lexeme ?? '';
      var nameOffset = nameToken?.offset ?? -1;
      if (node.parameter is FieldFormalParameter) {
        // Only for recovery, this should not happen in valid code.
        element = DefaultFieldFormalParameterElementImpl(
          name: name,
          nameOffset: nameOffset,
          parameterKind: node.kind,
        )..constantInitializer = node.defaultValue;
      } else if (node.parameter is SuperFormalParameter) {
        // Only for recovery, this should not happen in valid code.
        element = DefaultSuperFormalParameterElementImpl(
          name: name,
          nameOffset: nameOffset,
          parameterKind: node.kind,
        )..constantInitializer = node.defaultValue;
      } else {
        element = DefaultParameterElementImpl(
          name: name,
          nameOffset: nameOffset,
          parameterKind: node.kind,
        )..constantInitializer = node.defaultValue;
      }
      _elementHolder.addParameter(element);

      _setCodeRange(element, node);
      element.isConst = node.isConst;
      element.isExplicitlyCovariant = node.parameter.covariantKeyword != null;
      element.isFinal = node.isFinal;

      if (normalParameter is SimpleFormalParameterImpl &&
          normalParameter.type == null) {
        element.hasImplicitType = true;
      }
    }

    normalParameter.declaredElement = element;
    node.declaredElement = element;

    normalParameter.accept(this);

    var defaultValue = node.defaultValue;
    if (defaultValue != null) {
      _withElementWalker(null, () {
        _withElementHolder(ElementHolder(element), () {
          defaultValue.accept(this);
        });
      });
    }
  }

  @override
  void visitEnumConstantDeclaration(
      covariant EnumConstantDeclarationImpl node) {
    var element = _elementWalker!.getVariable() as ConstFieldElementImpl;
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    var arguments = node.arguments;
    if (arguments != null) {
      _withElementWalker(null, () {
        _withElementHolder(ElementHolder(element), () {
          arguments.accept(this);
        });
      });
    }
  }

  @override
  void visitEnumDeclaration(covariant EnumDeclarationImpl node) {
    EnumElementImpl element = _elementWalker!.getEnum();
    node.declaredElement = element;
    _namedTypeResolver.enclosingClass = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forEnum(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);

        _resolveWithClause(
          declaration: node,
          clause: node.withClause,
        );
        _resolveImplementsClause(
          declaration: node,
          clause: node.implementsClause,
        );

        _defineElements(element.accessors);
        _defineElements(element.methods);
        node.constants.accept(this);
        node.members.accept(this);
      });
    });

    _namedTypeResolver.enclosingClass = null;
  }

  @override
  void visitExportDirective(covariant ExportDirectiveImpl node) {
    var element = node.element;
    if (element is LibraryExportElementImpl) {
      _setOrCreateMetadataElements(element, node.metadata);
    }

    _withElementWalker(null, () {
      super.visitExportDirective(node);
    });
  }

  @override
  void visitExtensionDeclaration(ExtensionDeclaration node) {
    var element = _elementWalker!.getExtension();
    (node as ExtensionDeclarationImpl).declaredElement = element;
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forExtension(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);
        node.onClause?.accept(this);

        _defineElements(element.accessors);
        _defineElements(element.methods);
        node.members.accept(this);
      });
    });
  }

  @override
  void visitExtensionTypeDeclaration(
    covariant ExtensionTypeDeclarationImpl node,
  ) {
    var element = _elementWalker!.getExtensionType();
    node.declaredElement = element;
    _namedTypeResolver.enclosingClass = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _setOrCreateMetadataElements(
      element.representation,
      node.representation.fieldMetadata,
    );

    _withElementWalker(ElementWalker.forExtensionType(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);

        node.representation.accept(this);
        _resolveImplementsClause(
          declaration: node,
          clause: node.implementsClause,
        );

        _defineElements(element.accessors);
        _defineElements(element.methods);
        node.members.accept(this);
      });
    });

    _namedTypeResolver.enclosingClass = null;
  }

  @override
  void visitFieldFormalParameter(covariant FieldFormalParameterImpl node) {
    FieldFormalParameterElementImpl element;
    if (node.parent is DefaultFormalParameter) {
      element = node.declaredElement as FieldFormalParameterElementImpl;
    } else {
      var nameToken = node.name;
      if (_elementWalker != null) {
        element =
            _elementWalker!.getParameter() as FieldFormalParameterElementImpl;
      } else {
        // Only for recovery, this should not happen in valid code.
        element = FieldFormalParameterElementImpl(
          name: nameToken.lexeme,
          nameOffset: nameToken.offset,
          parameterKind: node.kind,
        );
        _elementHolder.enclose(element);
        element.isConst = node.isConst;
        element.isExplicitlyCovariant = node.covariantKeyword != null;
        element.isFinal = node.isFinal;
        _setCodeRange(element, node);
      }
      node.declaredElement = element;
    }

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementHolder(ElementHolder(element), () {
      _withElementWalker(
        _elementWalker != null ? ElementWalker.forParameter(element) : null,
        () {
          _withNameScope(() {
            _buildTypeParameterElements(node.typeParameters);
            node.typeParameters?.accept(this);
            node.type?.accept(this);
            if (_elementWalker != null) {
              node.parameters?.accept(this);
            } else {
              // Only for recovery, this should not happen in valid code.
              element.type = node.type?.type ?? _dynamicType;
              _withElementWalker(null, () {
                node.parameters?.accept(this);
              });
            }
          });
        },
      );
    });
  }

  @override
  void visitForEachPartsWithPattern(
    covariant ForEachPartsWithPatternImpl node,
  ) {
    _patternVariables.casePatternStart();
    super.visitForEachPartsWithPattern(node);
    var variablesMap = _patternVariables.casePatternFinish();
    node.variables = variablesMap.values
        .whereType<BindPatternVariableElementImpl2>()
        .map((e) => e.asElement)
        .toList();
  }

  @override
  void visitForElement(covariant ForElementImpl node) {
    _withNameScope(() {
      super.visitForElement(node);
    });
  }

  @override
  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
    _withNameScope(() {
      super.visitForPartsWithDeclarations(node);
    });
  }

  @override
  void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) {
    var expression = node.functionExpression;

    ExecutableElementImpl element;
    if (_elementWalker != null) {
      element = node.isGetter || node.isSetter
          ? _elementWalker!.getAccessor()
          : _elementWalker!.getFunction();
      node.declaredElement = element;
      expression.declaredElement = element;
      expression.declaredFragment = element;
    } else {
      var functionElement = node.declaredElement as FunctionElementImpl;
      functionElement.element = LocalFunctionElementImpl(functionElement);

      element = functionElement;
      expression.declaredElement = functionElement;
      expression.declaredFragment = functionElement;

      _setCodeRange(element, node);
      setElementDocumentationComment(element, node);

      var body = node.functionExpression.body;
      if (node.externalKeyword != null || body is NativeFunctionBody) {
        element.isExternal = true;
      }

      element.isAsynchronous = body.isAsynchronous;
      element.isGenerator = body.isGenerator;
      if (node.returnType == null) {
        element.hasImplicitReturnType = true;
      }
    }

    _setOrCreateMetadataElements(element, node.metadata);

    var holder = ElementHolder(element);
    _withElementHolder(holder, () {
      _withElementWalker(
        _elementWalker != null ? ElementWalker.forExecutable(element) : null,
        () {
          _withNameScope(() {
            _buildTypeParameterElements(expression.typeParameters);
            expression.typeParameters?.accept(this);
            if (_elementWalker == null) {
              element.typeParameters = holder.typeParameters;
            }

            expression.parameters?.accept(this);
            if (_elementWalker == null) {
              element.parameters = holder.parameters;
            }

            node.returnType?.accept(this);
            if (_elementWalker == null) {
              element.returnType = node.returnType?.type ?? _dynamicType;
            }

            _defineParameters(element.parameters);
            _withElementWalker(null, () {
              expression.body.accept(this);
            });
          });
        },
      );
    });
  }

  @override
  void visitFunctionDeclarationStatement(
      covariant FunctionDeclarationStatementImpl node) {
    if (!_hasLocalElementsBuilt(node)) {
      _buildLocalFunctionElement(node);
    }

    node.functionDeclaration.accept(this);
  }

  @override
  void visitFunctionExpression(covariant FunctionExpressionImpl node) {
    var fragment = FunctionElementImpl.forOffset(node.offset);
    fragment.element = LocalFunctionElementImpl(fragment);

    _elementHolder.enclose(fragment);
    node.declaredElement = fragment;
    node.declaredFragment = fragment;

    fragment.hasImplicitReturnType = true;
    fragment.returnType = DynamicTypeImpl.instance;

    FunctionBody body = node.body;
    fragment.isAsynchronous = body.isAsynchronous;
    fragment.isGenerator = body.isGenerator;

    var holder = ElementHolder(fragment);
    _withElementHolder(holder, () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);
        fragment.typeParameters = holder.typeParameters;

        node.parameters!.accept(this);
        fragment.parameters = holder.parameters;

        _defineParameters(fragment.parameters);
        node.body.accept(this);
      });
    });

    _setCodeRange(fragment, node);
  }

  @override
  void visitFunctionTypeAlias(covariant FunctionTypeAliasImpl node) {
    var element = _elementWalker!.getTypedef();
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forTypedef(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);
        node.returnType?.accept(this);
        node.parameters.accept(this);
      });
    });
  }

  @override
  void visitFunctionTypedFormalParameter(
    covariant FunctionTypedFormalParameterImpl node,
  ) {
    ParameterElementImpl element;
    if (node.parent is DefaultFormalParameter) {
      element = node.declaredElement as ParameterElementImpl;
    } else {
      var nameToken = node.name;
      if (_elementWalker != null) {
        element = _elementWalker!.getParameter();
      } else {
        element = ParameterElementImpl(
          name: nameToken.lexeme,
          nameOffset: nameToken.offset,
          parameterKind: node.kind,
        );
        _elementHolder.addParameter(element);
        element.isConst = node.isConst;
        element.isExplicitlyCovariant = node.covariantKeyword != null;
        element.isFinal = node.isFinal;
        _setCodeRange(element, node);
      }
      node.declaredElement = element;
    }

    _setOrCreateMetadataElements(element, node.metadata);

    var holder = ElementHolder(element);
    _withElementHolder(holder, () {
      _withElementWalker(
        _elementWalker != null ? ElementWalker.forParameter(element) : null,
        () {
          _withNameScope(() {
            _buildTypeParameterElements(node.typeParameters);
            node.typeParameters?.accept(this);
            if (_elementWalker == null) {
              element.typeParameters = holder.typeParameters;
            }

            node.parameters.accept(this);
            if (_elementWalker == null) {
              element.parameters = holder.parameters;
            }

            node.returnType?.accept(this);
            if (_elementWalker == null) {
              element.type = FunctionTypeImpl(
                typeFormals: element.typeParameters,
                parameters: element.parameters,
                returnType: node.returnType?.type ?? _dynamicType,
                nullabilitySuffix: _getNullability(node.question != null),
              );
            }
          });
        },
      );
    });
  }

  @override
  void visitGenericFunctionType(GenericFunctionType node) {
    var element = GenericFunctionTypeElementImpl.forOffset(node.offset);
    _unitElement.encloseElement(element);
    (node as GenericFunctionTypeImpl).declaredElement = element;

    element.isNullable = node.question != null;

    _setCodeRange(element, node);

    var holder = ElementHolder(element);
    _withElementHolder(holder, () {
      _withElementWalker(null, () {
        _withNameScope(() {
          _buildTypeParameterElements(node.typeParameters);
          node.typeParameters?.accept(this);
          element.typeParameters = holder.typeParameters;

          node.parameters.accept(this);
          element.parameters = holder.parameters;

          node.returnType?.accept(this);
          element.returnType = node.returnType?.type ?? _dynamicType;
        });
      });
    });

    var type = FunctionTypeImpl(
      typeFormals: element.typeParameters,
      parameters: element.parameters,
      returnType: element.returnType,
      nullabilitySuffix: _getNullability(node.question != null),
    );
    element.type = type;
    node.type = type;
  }

  @override
  void visitGenericTypeAlias(covariant GenericTypeAliasImpl node) {
    var element = _elementWalker!.getTypedef();
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forGenericTypeAlias(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);
        node.type.accept(this);
      });
    });
  }

  @override
  void visitIfElement(covariant IfElementImpl node) {
    _withNameScope(() {
      _visitIf(node);
    });
  }

  @override
  void visitIfStatement(covariant IfStatementImpl node) {
    _visitIf(node);
  }

  @override
  void visitImportDirective(covariant ImportDirectiveImpl node) {
    var element = node.element;
    if (element is LibraryImportElementImpl) {
      _setOrCreateMetadataElements(element, node.metadata);
    }

    _withElementWalker(null, () {
      super.visitImportDirective(node);
    });
  }

  @override
  void visitInstanceCreationExpression(
    covariant InstanceCreationExpressionImpl node,
  ) {
    var newNode = _astRewriter.instanceCreationExpression(_nameScope, node);
    if (newNode != node) {
      if (node.constructorName.type.typeArguments != null &&
          newNode is MethodInvocation &&
          newNode.target is FunctionReference &&
          !_libraryElement.featureSet.isEnabled(Feature.constructor_tearoffs)) {
        // A function reference with explicit type arguments (an expression of
        // the form `a<...>.m(...)` or `p.a<...>.m(...)` where `a` does not
        // refer to a class name, nor a type alias), is illegal without the
        // constructor tearoff feature.
        //
        // This is a case where the parser does not report an error, because the
        // parser thinks this could be an InstanceCreationExpression.
        _errorReporter.atNode(
          node,
          WarningCode.SDK_VERSION_CONSTRUCTOR_TEAROFFS,
        );
      }
      return newNode.accept(this);
    }

    super.visitInstanceCreationExpression(node);
  }

  @override
  void visitLabeledStatement(LabeledStatement node) {
    _buildLabelElements(node.labels, false);

    var outerScope = _labelScope;
    try {
      var unlabeled = node.unlabeled;
      for (Label label in node.labels) {
        SimpleIdentifier labelNameNode = label.label;
        _labelScope = LabelScope(
          _labelScope,
          labelNameNode.name,
          unlabeled,
          labelNameNode.staticElement as LabelElement,
        );
      }
      unlabeled.accept(this);
    } finally {
      _labelScope = outerScope;
    }
  }

  @override
  void visitLibraryDirective(covariant LibraryDirectiveImpl node) {
    ++_libraryDirectiveIndex;
    var element = node.element;
    if (element is LibraryElementImpl && _libraryDirectiveIndex == 1) {
      _setOrCreateMetadataElements(element, node.metadata);
    }

    _withElementWalker(null, () {
      super.visitLibraryDirective(node);
    });
  }

  @override
  void visitLogicalAndPattern(covariant LogicalAndPatternImpl node) {
    node.leftOperand.accept(this);
    node.rightOperand.accept(this);
  }

  @override
  void visitLogicalOrPattern(covariant LogicalOrPatternImpl node) {
    _patternVariables.logicalOrPatternStart();
    node.leftOperand.accept(this);
    _patternVariables.logicalOrPatternFinishLeft();
    node.rightOperand.accept(this);
    _patternVariables.logicalOrPatternFinish(node);
  }

  @override
  void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
    ExecutableElementImpl element = node.isGetter || node.isSetter
        ? _elementWalker!.getAccessor()
        : _elementWalker!.getFunction();
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forExecutable(element), () {
      node.metadata.accept(this);
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);
        node.parameters?.accept(this);
        node.returnType?.accept(this);

        _withElementWalker(null, () {
          _withElementHolder(ElementHolder(element), () {
            _defineParameters(element.parameters);
            node.body.accept(this);
          });
        });
      });
    });
  }

  @override
  void visitMethodInvocation(covariant MethodInvocationImpl node) {
    var newNode = _astRewriter.methodInvocation(_nameScope, node);
    if (newNode != node) {
      return newNode.accept(this);
    }

    super.visitMethodInvocation(node);
  }

  @override
  void visitMixinDeclaration(covariant MixinDeclarationImpl node) {
    var element = _elementWalker!.getMixin();
    node.declaredElement = element;

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementWalker(ElementWalker.forMixin(element), () {
      _withNameScope(() {
        _buildTypeParameterElements(node.typeParameters);
        node.typeParameters?.accept(this);

        _resolveOnClause(
          declaration: node,
          clause: node.onClause,
        );
        _resolveImplementsClause(
          declaration: node,
          clause: node.implementsClause,
        );

        _defineElements(element.accessors);
        _defineElements(element.methods);
        node.members.accept(this);
      });
    });
  }

  @override
  void visitNamedType(covariant NamedTypeImpl node) {
    node.typeArguments?.accept(this);

    _namedTypeResolver.nameScope = _nameScope;
    _namedTypeResolver.resolve(node, dataForTesting: dataForTesting);

    if (_namedTypeResolver.rewriteResult != null) {
      _namedTypeResolver.rewriteResult!.accept(this);
    }
  }

  @override
  void visitPartDirective(covariant PartDirectiveImpl node) {
    var element = node.element;
    if (element is PartElementImpl) {
      _setOrCreateMetadataElements(element, node.metadata);
    }

    _withElementWalker(null, () {
      super.visitPartDirective(node);
    });
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    _withElementWalker(null, () {
      super.visitPartOfDirective(node);
    });
  }

  @override
  void visitPatternAssignment(PatternAssignment node) {
    // We need to call `casePatternStart` and `casePatternFinish` in case there
    // are any declared variable patterns inside the pattern assignment (this
    // could happen due to error recovery).  But we don't need to keep the
    // variables map that `casePatternFinish` returns.
    _patternVariables.casePatternStart();
    super.visitPatternAssignment(node);
    _patternVariables.casePatternFinish();
  }

  @override
  void visitPatternVariableDeclaration(
    covariant PatternVariableDeclarationImpl node,
  ) {
    _patternVariables.casePatternStart();
    super.visitPatternVariableDeclaration(node);
    var variablesMap = _patternVariables.casePatternFinish();
    node.elements = variablesMap.values
        .whereType<BindPatternVariableElementImpl2>()
        .map((e) => e.asElement)
        .toList();
  }

  @override
  void visitPrefixedIdentifier(covariant PrefixedIdentifierImpl node) {
    var newNode = _astRewriter.prefixedIdentifier(_nameScope, node);
    if (newNode != node) {
      return newNode.accept(this);
    }

    super.visitPrefixedIdentifier(node);
  }

  @override
  void visitPropertyAccess(covariant PropertyAccessImpl node) {
    var newNode = _astRewriter.propertyAccess(_nameScope, node);
    if (newNode != node) {
      return newNode.accept(this);
    }

    super.visitPropertyAccess(node);
  }

  @override
  void visitRecordTypeAnnotation(covariant RecordTypeAnnotationImpl node) {
    _withElementWalker(null, () {
      node.visitChildren(this);
    });

    _recordTypeResolver.resolve(node);
  }

  @override
  void visitRepresentationDeclaration(
    covariant RepresentationDeclarationImpl node,
  ) {
    var element = _elementWalker!.element as ExtensionTypeElementImpl;
    if (element.augmentationTarget == null) {
      node.fieldElement = _elementWalker!.getVariable() as FieldElementImpl;
      node.constructorElement = _elementWalker!.getConstructor();
    }

    super.visitRepresentationDeclaration(node);
  }

  @override
  void visitSimpleFormalParameter(covariant SimpleFormalParameterImpl node) {
    ParameterElementImpl element;
    if (node.parent is DefaultFormalParameter) {
      element = node.declaredElement as ParameterElementImpl;
    } else {
      var nameToken = node.name;
      if (_elementWalker != null) {
        element = _elementWalker!.getParameter();
      } else {
        if (nameToken != null) {
          element = ParameterElementImpl(
            name: nameToken.lexeme,
            nameOffset: nameToken.offset,
            parameterKind: node.kind,
          );
        } else {
          element = ParameterElementImpl(
            name: '',
            nameOffset: -1,
            parameterKind: node.kind,
          );
        }
        _elementHolder.addParameter(element);

        _setCodeRange(element, node);
        element.isConst = node.isConst;
        element.isExplicitlyCovariant = node.covariantKeyword != null;
        element.isFinal = node.isFinal;
        if (node.type == null) {
          element.hasImplicitType = true;
        }
        node.declaredElement = element;
      }
      node.declaredElement = element;
    }

    node.type?.accept(this);
    if (_elementWalker == null) {
      element.type = node.type?.type ?? _dynamicType;
    }

    _setOrCreateMetadataElements(element, node.metadata);
  }

  @override
  void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
    var newNode = _astRewriter.simpleIdentifier(_nameScope, node);
    if (newNode != node) {
      return newNode.accept(this);
    }

    super.visitSimpleIdentifier(node);
  }

  @override
  void visitSuperFormalParameter(covariant SuperFormalParameterImpl node) {
    SuperFormalParameterElementImpl element;
    if (node.parent is DefaultFormalParameter) {
      element = node.declaredElement as SuperFormalParameterElementImpl;
    } else {
      var nameToken = node.name;
      if (_elementWalker != null) {
        element =
            _elementWalker!.getParameter() as SuperFormalParameterElementImpl;
      } else {
        // Only for recovery, this should not happen in valid code.
        element = SuperFormalParameterElementImpl(
          name: nameToken.lexeme,
          nameOffset: nameToken.offset,
          parameterKind: node.kind,
        );
        _elementHolder.enclose(element);
        element.isConst = node.isConst;
        element.isExplicitlyCovariant = node.covariantKeyword != null;
        element.isFinal = node.isFinal;
        _setCodeRange(element, node);
      }
      node.declaredElement = element;
    }

    _setOrCreateMetadataElements(element, node.metadata);

    _withElementHolder(ElementHolder(element), () {
      _withElementWalker(
        _elementWalker != null ? ElementWalker.forParameter(element) : null,
        () {
          _withNameScope(() {
            _buildTypeParameterElements(node.typeParameters);
            node.typeParameters?.accept(this);
            node.type?.accept(this);
            if (_elementWalker != null) {
              node.parameters?.accept(this);
            } else {
              // Only for recovery, this should not happen in valid code.
              element.type = node.type?.type ?? _dynamicType;
              _withElementWalker(null, () {
                node.parameters?.accept(this);
              });
            }
          });
        },
      );
    });
  }

  @override
  void visitSwitchCase(SwitchCase node) {
    throw StateError('Should not be invoked');
  }

  @override
  void visitSwitchDefault(SwitchDefault node) {
    throw StateError('Should not be invoked');
  }

  @override
  void visitSwitchExpression(covariant SwitchExpressionImpl node) {
    node.expression.accept(this);

    for (var case_ in node.cases) {
      _withNameScope(() {
        _resolveGuardedPattern(
          case_.guardedPattern,
          then: () {
            case_.expression.accept(this);
          },
        );
      });
    }
  }

  @override
  void visitSwitchExpressionCase(SwitchExpressionCase node) {
    throw StateError('Should not be invoked');
  }

  @override
  void visitSwitchPatternCase(SwitchPatternCase node) {
    throw StateError('Should not be invoked');
  }

  @override
  void visitSwitchStatement(covariant SwitchStatementImpl node) {
    node.expression.accept(this);

    for (var group in node.memberGroups) {
      _patternVariables.switchStatementSharedCaseScopeStart(group);
      for (var member in group.members) {
        _buildLabelElements(member.labels, true);
        if (member is SwitchCaseImpl) {
          member.expression.accept(this);
        } else if (member is SwitchDefaultImpl) {
          _patternVariables.switchStatementSharedCaseScopeEmpty(group);
        } else if (member is SwitchPatternCaseImpl) {
          _resolveGuardedPattern(
            member.guardedPattern,
            sharedCaseScopeKey: group,
          );
        } else {
          throw UnimplementedError('(${member.runtimeType}) $member');
        }
      }
      if (group.hasLabels) {
        _patternVariables.switchStatementSharedCaseScopeEmpty(group);
      }
      group.variables =
          _patternVariables.switchStatementSharedCaseScopeFinish(group);
      _withNameScope(() {
        var statements = group.statements;
        _buildLocalElements(statements);
        statements.accept(this);
      });
    }
  }

  @override
  void visitTypeParameter(covariant TypeParameterImpl node) {
    var element = node.declaredElement as TypeParameterElementImpl;

    _setOrCreateMetadataElements(element, node.metadata);

    var boundNode = node.bound;
    if (boundNode != null) {
      boundNode.accept(this);
      if (_elementWalker == null) {
        element.bound = boundNode.type;
      }
    }
  }

  @override
  void visitVariableDeclaration(covariant VariableDeclarationImpl node) {
    var initializerNode = node.initializer;

    VariableElementImpl element;
    if (_elementWalker != null) {
      element = _elementWalker!.getVariable();
      node.declaredElement = element;
    } else {
      var localElement = node.declaredElement as LocalVariableElementImpl;
      element = localElement;

      var varList = node.parent as VariableDeclarationListImpl;
      localElement.hasImplicitType = varList.type == null;
      localElement.hasInitializer = initializerNode != null;
      localElement.type = varList.type?.type ?? _dynamicType;
    }

    if (initializerNode != null) {
      _withElementWalker(null, () {
        _withElementHolder(ElementHolder(element), () {
          initializerNode.accept(this);
        });
      });
    }
  }

  @override
  void visitVariableDeclarationList(
    covariant VariableDeclarationListImpl node,
  ) {
    var parent = node.parent;
    if (parent is ForPartsWithDeclarations ||
        parent is VariableDeclarationStatement &&
            !_hasLocalElementsBuilt(parent)) {
      _buildLocalVariableElements(node);
    }

    node.visitChildren(this);

    NodeList<AnnotationImpl> annotations;
    if (parent is FieldDeclarationImpl) {
      annotations = parent.metadata;
    } else if (parent is TopLevelVariableDeclarationImpl) {
      annotations = parent.metadata;
    } else {
      // Local variable declaration
      annotations = node.metadata;
    }

    var variables = node.variables;
    for (var i = 0; i < variables.length; i++) {
      var variable = variables[i];
      var element = variable.declaredElement as ElementImpl;
      _setOrCreateMetadataElements(element, annotations, visitNodes: false);

      var offset = (i == 0 ? node.parent! : variable).offset;
      var length = variable.end - offset;
      element.setCodeRange(offset, length);
    }
  }

  /// Builds the label elements associated with [labels] and stores them in the
  /// element holder.
  void _buildLabelElements(List<Label> labels, bool onSwitchMember) {
    for (var label in labels) {
      label as LabelImpl;
      var labelName = label.label;
      var element = LabelElementImpl(
        labelName.name,
        labelName.offset,
        onSwitchMember,
      );
      labelName.staticElement = element;
      _elementHolder.enclose(element);
    }
  }

  void _buildLocalElements(List<Statement> statements) {
    for (var statement in statements) {
      if (statement is FunctionDeclarationStatementImpl) {
        _buildLocalFunctionElement(statement);
      } else if (statement is VariableDeclarationStatement) {
        _buildLocalVariableElements(statement.variables);
      }
    }
  }

  void _buildLocalFunctionElement(
      covariant FunctionDeclarationStatementImpl statement) {
    var node = statement.functionDeclaration;
    var nameToken = node.name;

    var fragment = FunctionElementImpl(nameToken.lexeme, nameToken.offset);
    fragment.name2 = nameToken.nameIfNotEmpty;
    fragment.nameOffset2 = nameToken.offsetIfNotEmpty;
    node.declaredElement = fragment;

    fragment.element = LocalFunctionElementImpl(fragment);
    node.functionExpression.declaredFragment = fragment;

    // The fragment's old enclosing element needs to be set before we can get
    // the new element for it.
    _elementHolder.enclose(fragment);

    if (!_isWildCardVariable(nameToken.lexeme)) {
      _define(fragment);
    }
  }

  void _buildLocalVariableElements(VariableDeclarationList variableList) {
    var isConst = variableList.isConst;
    var isFinal = variableList.isFinal;
    var isLate = variableList.isLate;
    for (var variable in variableList.variables) {
      variable as VariableDeclarationImpl;
      var nameToken = variable.name;

      LocalVariableElementImpl element;
      if (isConst && variable.initializer != null) {
        element = ConstLocalVariableElementImpl(
          nameToken.lexeme,
          nameToken.offset,
        );
      } else {
        element = LocalVariableElementImpl(
          nameToken.lexeme,
          nameToken.offset,
        );
      }
      variable.declaredElement = element;
      _elementHolder.enclose(element);
      _define(element);

      element.isConst = isConst;
      element.isFinal = isFinal;
      element.isLate = isLate;
    }
  }

  /// Ensure that each type parameters from the [typeParameterList] has its
  /// element set, either from the [_elementWalker] or new, and define these
  /// elements in the [_nameScope].
  void _buildTypeParameterElements(TypeParameterList? typeParameterList) {
    if (typeParameterList == null) return;

    for (var typeParameter in typeParameterList.typeParameters) {
      typeParameter as TypeParameterImpl;
      var name = typeParameter.name;

      TypeParameterElementImpl element;
      if (_elementWalker != null) {
        element = _elementWalker!.getTypeParameter();
      } else {
        element = TypeParameterElementImpl(name.lexeme, name.offset);
        element.name2 = name.lexeme;
        element.nameOffset2 = name.offset;
        _elementHolder.addTypeParameter(element);

        _setCodeRange(element, typeParameter);
      }
      typeParameter.declaredElement = element;

      if (!_isWildCardVariable(element.name)) {
        _define(element);
      }

      _setOrCreateMetadataElements(element, typeParameter.metadata);
    }
  }

  /// Create a new [ElementAnnotation] for the [node].
  void _createElementAnnotation(AnnotationImpl node) {
    var element = ElementAnnotationImpl(_unitElement);
    element.annotationAst = node;
    node.elementAnnotation = element;
  }

  void _define(Element element) {
    if (_nameScope case LocalScope nameScope) {
      nameScope.add(element.asElement2!);
    }
  }

  /// Define given [elements] in the [_nameScope].
  void _defineElements(List<Element> elements) {
    int length = elements.length;
    for (int i = 0; i < length; i++) {
      var element = elements[i];
      _define(element);
    }
  }

  /// Define given [parameters] in the [_nameScope].
  void _defineParameters(List<ParameterElement> parameters) {
    int length = parameters.length;
    for (int i = 0; i < length; i++) {
      ParameterElement parameter = parameters[i];
      if (!parameter.isInitializingFormal) {
        _define(parameter);
      }
    }
  }

  NullabilitySuffix _getNullability(bool hasQuestion) {
    if (hasQuestion) {
      return NullabilitySuffix.question;
    } else {
      return NullabilitySuffix.none;
    }
  }

  bool _isWildCardVariable(String name) =>
      name == '_' &&
      _libraryElement.featureSet.isEnabled(Feature.wildcard_variables);

  void _resolveGuardedPattern(
    GuardedPatternImpl guardedPattern, {
    Object? sharedCaseScopeKey,
    void Function()? then,
  }) {
    _patternVariables.casePatternStart();
    guardedPattern.pattern.accept(this);
    var variables = _patternVariables.casePatternFinish(
      sharedCaseScopeKey: sharedCaseScopeKey,
    );
    // Matched variables are available in `whenClause`.
    _withNameScope(() {
      for (var variable in variables.values) {
        _define(variable.asElement!);
      }
      guardedPattern.variables = variables.cast();
      guardedPattern.whenClause?.accept(this);
      if (then != null) {
        then();
      }
    });
  }

  void _resolveImplementsClause({
    required Declaration? declaration,
    required ImplementsClauseImpl? clause,
  }) {
    if (clause == null) return;

    _resolveTypes(
      declaration: declaration,
      clause: clause,
      namedTypes: clause.interfaces,
    );
  }

  void _resolveOnClause({
    required Declaration? declaration,
    required MixinOnClauseImpl? clause,
  }) {
    if (clause == null) return;

    _resolveTypes(
      declaration: declaration,
      clause: clause,
      namedTypes: clause.superclassConstraints,
    );
  }

  void _resolveRedirectedConstructor(ConstructorDeclaration node) {
    var redirectedConstructor = node.redirectedConstructor;
    if (redirectedConstructor == null) return;

    var namedType = redirectedConstructor.type;
    _namedTypeResolver.redirectedConstructor_namedType = namedType;

    redirectedConstructor.accept(this);

    _namedTypeResolver.redirectedConstructor_namedType = null;
  }

  /// Resolves the given [namedType], reports errors if the resulting type
  /// is not valid in the context of the [declaration] and [clause].
  void _resolveType({
    required Declaration? declaration,
    required AstNode? clause,
    required NamedTypeImpl namedType,
  }) {
    _namedTypeResolver.classHierarchy_namedType = namedType;
    visitNamedType(namedType);
    _namedTypeResolver.classHierarchy_namedType = null;

    if (_namedTypeResolver.hasErrorReported) {
      return;
    }

    var type = namedType.typeOrThrow;

    var enclosingElement = _namedTypeResolver.enclosingClass;
    if (enclosingElement is ExtensionTypeElementImpl) {
      _verifyExtensionElementImplements(enclosingElement, namedType, type);
      return;
    }

    var element = type.element;
    switch (element) {
      case ClassElement():
        return;
      case MixinElement():
        if (clause is ImplementsClause ||
            clause is MixinOnClause ||
            clause is WithClause) {
          return;
        }
    }

    if (_unitElement.shouldIgnoreUndefinedNamedType(namedType)) {
      return;
    }

    ErrorCode? errorCode;
    switch (clause) {
      case null:
        if (declaration is ClassTypeAlias) {
          errorCode = CompileTimeErrorCode.MIXIN_WITH_NON_CLASS_SUPERCLASS;
        }
      case ExtendsClause():
        if (declaration is ClassDeclaration) {
          errorCode = declaration.withClause == null
              ? CompileTimeErrorCode.EXTENDS_NON_CLASS
              : CompileTimeErrorCode.MIXIN_WITH_NON_CLASS_SUPERCLASS;
        }
      case ImplementsClause():
        errorCode = CompileTimeErrorCode.IMPLEMENTS_NON_CLASS;
      case MixinOnClause():
        errorCode =
            CompileTimeErrorCode.MIXIN_SUPER_CLASS_CONSTRAINT_NON_INTERFACE;
      case WithClause():
        errorCode = CompileTimeErrorCode.MIXIN_OF_NON_CLASS;
    }

    // Should not happen.
    if (errorCode == null) {
      assert(false);
      return;
    }

    var firstToken = namedType.importPrefix?.name ?? namedType.name2;
    var offset = firstToken.offset;
    var length = namedType.name2.end - offset;
    _errorReporter.atOffset(
      offset: offset,
      length: length,
      errorCode: errorCode,
    );
  }

  /// Resolve the types in the given list of type names.
  ///
  /// @param typeNames the type names to be resolved
  /// @param nonTypeError the error to produce if the type name is defined to be
  ///        something other than a type
  /// @param enumTypeError the error to produce if the type name is defined to
  ///        be an enum
  /// @param dynamicTypeError the error to produce if the type name is "dynamic"
  /// @return an array containing all of the types that were resolved.
  void _resolveTypes({
    required Declaration? declaration,
    required AstNode clause,
    required NodeList<NamedTypeImpl> namedTypes,
  }) {
    for (var namedType in namedTypes) {
      _resolveType(
        declaration: declaration,
        clause: clause,
        namedType: namedType,
      );
    }
  }

  void _resolveWithClause({
    required Declaration? declaration,
    required WithClauseImpl? clause,
  }) {
    if (clause == null) return;

    for (var namedType in clause.mixinTypes) {
      _namedTypeResolver.withClause_namedType = namedType;
      _resolveType(
        declaration: declaration,
        clause: clause,
        namedType: namedType,
      );
      _namedTypeResolver.withClause_namedType = null;
    }
  }

  void _setCodeRange(ElementImpl element, AstNode node) {
    element.setCodeRange(node.offset, node.length);
  }

  void _setOrCreateMetadataElements(
    ElementImpl element,
    NodeList<AnnotationImpl> annotations, {
    bool visitNodes = true,
  }) {
    if (visitNodes) {
      annotations.accept(this);
    }
    if (_elementWalker != null) {
      _setElementAnnotations(annotations, element.metadata);
    } else if (annotations.isNotEmpty) {
      element.metadata = annotations.map((annotation) {
        return annotation.elementAnnotation!;
      }).toList();
    }
  }

  void _verifyExtensionElementImplements(
    ExtensionTypeElementImpl declaredElement,
    NamedTypeImpl node,
    DartType type,
  ) {
    var typeSystem = _libraryElement.typeSystem;

    if (!typeSystem.isValidExtensionTypeSuperinterface(type)) {
      _errorReporter.atNode(
        node,
        CompileTimeErrorCode.EXTENSION_TYPE_IMPLEMENTS_DISALLOWED_TYPE,
        arguments: [type],
      );
      return;
    }

    var declaredRepresentation = declaredElement.representation.type;
    if (typeSystem.isSubtypeOf(declaredRepresentation, type)) {
      return;
    }

    // When `type` is an extension type.
    if (type is InterfaceTypeImpl) {
      var implementedRepresentation = type.representationType;
      if (implementedRepresentation != null) {
        if (!typeSystem.isSubtypeOf(
          declaredRepresentation,
          implementedRepresentation,
        )) {
          _errorReporter.atNode(
            node,
            CompileTimeErrorCode
                .EXTENSION_TYPE_IMPLEMENTS_REPRESENTATION_NOT_SUPERTYPE,
            arguments: [
              implementedRepresentation,
              type.element.name,
              declaredRepresentation,
              declaredElement.name,
            ],
          );
        }
        return;
      }
    }

    _errorReporter.atNode(
      node,
      CompileTimeErrorCode.EXTENSION_TYPE_IMPLEMENTS_NOT_SUPERTYPE,
      arguments: [type, declaredRepresentation],
    );
  }

  void _visitIf(IfElementOrStatementImpl node) {
    var caseClause = node.caseClause;
    if (caseClause != null) {
      node.expression.accept(this);
      _resolveGuardedPattern(caseClause.guardedPattern, then: () {
        node.ifTrue.accept(this);
      });
      node.ifFalse?.accept(this);
    } else {
      node.visitChildren(this);
    }
  }

  /// Make the given [holder] be the current one while running [f].
  void _withElementHolder(ElementHolder holder, void Function() f) {
    var previousHolder = _elementHolder;
    _elementHolder = holder;
    try {
      f();
    } finally {
      _elementHolder = previousHolder;
    }
  }

  /// Make the given [walker] be the current one while running [f].
  void _withElementWalker(ElementWalker? walker, void Function() f) {
    var current = _elementWalker;
    try {
      _elementWalker = walker;
      f();
    } finally {
      _elementWalker = current;
    }
  }

  /// Run [f] with the new name scope.
  void _withNameScope(void Function() f) {
    var current = _nameScope;
    try {
      _nameScope = LocalScope(current);
      f();
    } finally {
      _nameScope = current;
    }
  }

  /// We always build local elements for [VariableDeclarationStatement]s and
  /// [FunctionDeclarationStatement]s in blocks, because invalid code might try
  /// to use forward references.
  static bool _hasLocalElementsBuilt(Statement node) {
    var parent = node.parent;
    return parent is Block || parent is SwitchMember;
  }

  /// Associate each of the annotation [nodes] with the corresponding
  /// [ElementAnnotation] in [annotations].
  static void _setElementAnnotations(
    List<AnnotationImpl> nodes,
    List<ElementAnnotationImpl> annotations,
  ) {
    int nodeCount = nodes.length;
    if (nodeCount != annotations.length) {
      throw StateError(
        'Found $nodeCount annotation nodes and '
        '${annotations.length} element annotations',
      );
    }
    for (int i = 0; i < nodeCount; i++) {
      nodes[i].elementAnnotation = annotations[i];
    }
  }
}

class _VariableBinder
    extends VariableBinder<DartPatternImpl, PromotableElementImpl2> {
  final TypeProvider typeProvider;

  _VariableBinder({
    required super.errors,
    required this.typeProvider,
  });

  @override
  JoinPatternVariableElementImpl2 joinPatternVariables({
    required Object key,
    required List<PromotableElement2> components,
    required shared.JoinedPatternVariableInconsistency inconsistency,
  }) {
    var first = components.first;
    List<PatternVariableElementImpl> expandedVariables;
    if (key is LogicalOrPatternImpl) {
      expandedVariables = components.expand((variable) {
        variable as PatternVariableElementImpl2;
        if (variable is JoinPatternVariableElementImpl2) {
          return variable.variables;
        } else {
          return [variable.asElement as PatternVariableElementImpl];
        }
      }).toList(growable: false);
    } else if (key is SwitchStatementCaseGroup) {
      expandedVariables = components
          .map((e) => e.asElement as PatternVariableElementImpl)
          .toList(growable: false);
    } else {
      throw UnimplementedError('(${key.runtimeType}) $key');
    }
    return (JoinPatternVariableElementImpl(
      first.name3!,
      -1,
      expandedVariables,
      inconsistency.maxWithAll(
        components
            .whereType<JoinPatternVariableElementImpl2>()
            .map((e) => e.inconsistency),
      ),
    )
          ..enclosingElement3 =
              first.firstFragment.enclosingFragment!.element.asElement!
          ..type = InvalidTypeImpl.instance)
        .element;
  }
}

class _VariableBinderErrors
    implements VariableBinderErrors<DartPatternImpl, PromotableElementImpl2> {
  final ResolutionVisitor visitor;

  _VariableBinderErrors(this.visitor);

  @override
  void assertInErrorRecovery() {
    // TODO(scheglov): implement assertInErrorRecovery
    throw UnimplementedError();
  }

  @override
  void duplicateVariablePattern({
    required String name,
    required covariant BindPatternVariableElementImpl2 original,
    required covariant BindPatternVariableElementImpl2 duplicate,
  }) {
    visitor._errorReporter.reportError(
      DiagnosticFactory().duplicateDefinitionForNodes(
        visitor._errorReporter.source,
        CompileTimeErrorCode.DUPLICATE_VARIABLE_PATTERN,
        duplicate.node.name,
        original.node.name,
        [name],
      ),
    );
    duplicate.isDuplicate = true;
  }

  @override
  void logicalOrPatternBranchMissingVariable({
    required covariant LogicalOrPatternImpl node,
    required bool hasInLeft,
    required String name,
    required PromotableElement2 variable,
  }) {
    visitor._errorReporter.atNode(
      hasInLeft ? node.rightOperand : node.leftOperand,
      CompileTimeErrorCode.MISSING_VARIABLE_PATTERN,
      arguments: [name],
    );
  }
}

extension on Token {
  String? get nameIfNotEmpty {
    return lexeme.isNotEmpty ? lexeme : null;
  }

  int? get offsetIfNotEmpty {
    return lexeme.isNotEmpty ? offset : null;
  }
}
