visitClassDeclaration method

  1. @override
Object? visitClassDeclaration(
  1. SClassDeclaration node
)
override

Implementation

@override
Object? visitClassDeclaration(SClassDeclaration node) {
  final className = node.name!.name;
  Logger.debug(
    "[Visitor.visitClassDeclaration] START for '$className' in env: ${environment.hashCode}",
  );

  // Retrieve the placeholder class object created in Pass 1
  Object? placeholder = environment.get(className);
  if (placeholder == null || placeholder is! InterpretedClass) {
    // This should not happen if Pass 1 worked correctly
    throw StateD4rtException(
      "Placeholder for class '$className' not found or invalid during Pass 2.",
    );
  }
  final klass = placeholder;
  Logger.debug(
    "[Visitor.visitClassDeclaration] Retrieved placeholder for '$className' (hash: ${klass.hashCode})",
  );

  // Resolve and populate relationships ON THE EXISTING klass object
  // Superclass lookup
  // InterpretedClass? superclass; // Keep this commented or remove
  if (node.extendsClause != null) {
    final superclassName = node.extendsClause!.superclass!.name!.name;
    Logger.debug(
      "[Visitor.visitClassDeclaration]   Trying to get superclass '$superclassName' from env: ${environment.hashCode}",
    );
    Object? potentialSuperclass;
    try {
      potentialSuperclass = environment.get(superclassName);
    } on RuntimeD4rtException {
      throw RuntimeD4rtException(
        "Superclass '$superclassName' not found for class '$className'. Ensure it's defined.",
      );
    }

    if (potentialSuperclass is InterpretedClass) {
      // Standard Dart Superclass
      final superclass = potentialSuperclass;

      // Add checks for final and interface modifiers
      // Note: sealed and interface classes CAN be extended within the same library/file,
      // and in D4rt all interpreted code is considered the same library
      //
      // For 'final' classes:
      // - A 'final' class cannot be extended outside its library
      // - An 'abstract final' class CAN be extended within the same library (needs implementation)
      // - Since all D4rt code is considered the same library, we only block non-abstract final classes
      if (superclass.isFinal && !superclass.isAbstract) {
        throw RuntimeD4rtException(
          "Class '$className' cannot extend final class '$superclassName'.",
        );
      }
      // Interface classes can be extended within the same library, so we allow it in D4rt
      // sealed classes are also allowed to be extended - they restrict external libraries only

      // Set the superclass and clear bridged superclass
      klass.superclass = superclass;
      klass.bridgedSuperclass = null;
      Logger.debug(
        "[Visitor.visitClassDeclaration] Set standard superclass '$superclassName' for '$className'",
      );
    } else if (potentialSuperclass is BridgedClass) {
      final bridgedSuperclass = potentialSuperclass;
      // No modifier checks needed for bridged classes (yet?)

      // Set the bridged superclass and clear standard superclass
      klass.bridgedSuperclass = bridgedSuperclass;
      klass.superclass = null;

      // Capture the reified type arguments from the extends clause so
      // proxy factories can pick the right typed variant (e.g.
      // `extends CustomClipper<RRect>` → pick the RRect proxy, not the
      // default Path one). See [InterpretedClass.bridgedSuperTypeArgNames].
      final superTypeArgs =
          node.extendsClause!.superclass!.typeArguments?.arguments;
      if (superTypeArgs != null && superTypeArgs.isNotEmpty) {
        klass.bridgedSuperTypeArgNames = superTypeArgs
            .map((arg) => arg is SNamedType ? (arg.name?.name ?? '') : '')
            .toList();
      }

      Logger.debug(
        "[Visitor.visitClassDeclaration] Set BRIDGED superclass '$superclassName' for '$className' (typeArgs=${klass.bridgedSuperTypeArgNames})",
      );
    } else {
      throw RuntimeD4rtException(
        "Superclass '$superclassName' for class '$className' resolved to ${potentialSuperclass?.runtimeType}, which is not a class or bridged class.",
      );
    }
  }

  // Interface lookup
  if (node.implementsClause != null) {
    Logger.debug(
      "[Visitor.visitClassDeclaration] Processing 'implements' clause for '$className' in env: ${environment.hashCode}",
    );
    for (final interfaceType in node.implementsClause!.interfaces) {
      final interfaceName = interfaceType.name!.name;
      Logger.debug(
        "[Visitor.visitClassDeclaration]   Trying to get interface '$interfaceName' from env: ${environment.hashCode}",
      );
      try {
        final potentialInterface = environment.get(interfaceName);
        if (potentialInterface is InterpretedClass) {
          // Add checks for base modifier only
          // Note: sealed classes CAN be implemented within the same library/file
          if (potentialInterface.isBase) {
            throw RuntimeD4rtException(
              "Class '$className' cannot implement base class '$interfaceName' outside of its library.",
            );
          }
          // sealed classes are allowed to be implemented - they restrict external libraries only

          // Add to the interfaces list of the existing klass object
          klass.interfaces.add(potentialInterface);
          Logger.debug(
            "[Visitor.visitClassDeclaration] Added interface '$interfaceName' for '$className'",
          );
        } else if (potentialInterface is BridgedClass) {
          // Support for bridged interfaces like Comparable, Exception
          klass.bridgedInterfaces.add(potentialInterface);
          Logger.debug(
            "[Visitor.visitClassDeclaration] Added bridged interface '$interfaceName' for '$className'",
          );
        } else {
          throw RuntimeD4rtException(
            "Class '$className' cannot implement non-class '$interfaceName' (${potentialInterface?.runtimeType}).",
          );
        }
      } on RuntimeD4rtException {
        throw RuntimeD4rtException(
          "Interface '$interfaceName' not found for class '$className'. Ensure it's defined.",
        );
      }
    }
  }

  // Mixin application lookup
  if (node.withClause != null) {
    Logger.debug(
      "[Visitor.visitClassDeclaration] Processing 'with' clause for '$className' in env: ${environment.hashCode}",
    );
    for (final mixinType in node.withClause!.mixinTypes) {
      final mixinName = mixinType.name!.name;
      Logger.debug(
        "[Visitor.visitClassDeclaration]   Trying to get mixin '$mixinName' from env: ${environment.hashCode}",
      );

      Object? mixin;
      try {
        mixin = environment.get(mixinName);
      } on RuntimeD4rtException {
        throw RuntimeD4rtException(
          "Mixin '$mixinName' not found during lookup for class '$className'. Ensure it's defined (as a mixin or class mixin).",
        );
      }

      if (mixin is InterpretedClass) {
        if (!mixin.isMixin) {
          throw RuntimeD4rtException(
            "Class '$mixinName' cannot be used as a mixin because it's not declared with 'mixin' or 'class mixin'.",
          );
        }

        // Add checks for base modifier only
        // Note: sealed classes CAN be mixed in within the same library/file
        if (mixin.isBase) {
          throw RuntimeD4rtException(
            "Class '$className' cannot mix in base class '$mixinName' outside of its library.",
          );
        }
        // sealed classes are allowed to be mixed in - they restrict external libraries only

        // Add to the mixins list of the existing klass object
        klass.mixins.add(mixin);
        Logger.debug(
          "[Visitor.visitClassDeclaration] Applied interpreted mixin '$mixinName' to '$className'",
        );
      } else if (mixin is BridgedClass) {
        // Support for bridged classes as mixins
        if (!mixin.canBeUsedAsMixin) {
          throw RuntimeD4rtException(
            "Bridged class '$mixinName' cannot be used as a mixin. Set canBeUsedAsMixin=true when registering the bridge.",
          );
        }

        // Add to the bridged mixins list
        klass.bridgedMixins.add(mixin);
        Logger.debug(
          "[Visitor.visitClassDeclaration] Applied bridged mixin '$mixinName' to '$className'",
        );
      } else {
        throw RuntimeD4rtException(
          "Identifier '$mixinName' resolved to ${mixin?.runtimeType}, which is not a class/mixin, for class '$className'.",
        );
      }
    }
  }

  // Populate members ON THE EXISTING klass object
  final staticInitEnv = environment; // Statics use the class definition env
  final originalVisitorEnv = environment; // Backup visitor env

  Logger.debug(
    "[Visitor.visitClassDeclaration] Processing members for '$className' (hash: ${klass.hashCode})",
  );

  for (final member in node.members) {
    if (member is SMethodDeclaration) {
      final methodName = member.name!.name;
      // Pass the ALREADY RETRIEVED klass object
      final function = InterpretedFunction.method(
        member,
        staticInitEnv,
        klass,
      );

      if (member.isStatic) {
        if (member.isGetter) {
          klass.staticGetters[methodName] = function;
        } else if (member.isSetter) {
          klass.staticSetters[methodName] = function;
        } else {
          klass.staticMethods[methodName] = function;
        }
      } else if (!member.isAbstract) {
        if (member.isGetter) {
          klass.getters[methodName] = function;
        } else if (member.isSetter) {
          klass.setters[methodName] = function;
        } else if (member.isOperator) {
          // Add operator methods to the operators map
          klass.operators[methodName] = function;
        } else {
          klass.methods[methodName] = function;
        }
      } else {
        // Abstract
        if (!klass.isAbstract) {
          throw RuntimeD4rtException(
            "Abstract methods can only be declared in abstract classes. Method '${klass.name}.$methodName'.",
          );
        }
        if (member.body is! SEmptyFunctionBody) {
          throw RuntimeD4rtException(
            "Abstract methods cannot have a body. Method '${klass.name}.$methodName'.",
          );
        }
        if (member.isGetter) {
          klass.getters[methodName] = function;
        } else if (member.isSetter) {
          klass.setters[methodName] = function;
        } else if (member.isOperator) {
          // Add abstract operator methods to the operators map
          klass.operators[methodName] = function;
        } else {
          klass.methods[methodName] = function;
        }
      }
    } else if (member is SConstructorDeclaration) {
      final function = InterpretedFunction.constructor(
        member,
        staticInitEnv,
        klass,
      );
      final constructorName = member.name?.name ?? '';
      klass.constructors[constructorName] = function;
    } else if (member is SFieldDeclaration) {
      // Defer static field processing - collect them for later
      if (!member.isStatic) {
        klass.fieldDeclarations.add(member);
      }
    } else {
      throw StateD4rtException('Unknown member type: ${member.runtimeType}');
    }
  }

  // TWO-PASS STATIC FIELD PROCESSING to handle sibling references (Bug-23)
  // Pass 1: Register all static field names in the class environment with null values
  final staticFieldDecls = <SVariableDeclaration>[];
  final staticFieldMeta =
      <String, (bool isLate, bool isFinal, SAstNode? initializer)>{};
  for (final member in node.members) {
    if (member is SFieldDeclaration && member.isStatic) {
      final isLate = member.fields!.isLate;
      final isFinal = member.fields!.isFinal;
      for (final variable in member.fields!.variables) {
        final fieldName = variable.name!.name;
        staticFieldDecls.add(variable);
        staticFieldMeta[fieldName] = (isLate, isFinal, variable.initializer);
        // Pre-register all static fields so they can reference each other
        klass.staticFields[fieldName] = null;
      }
    }
  }

  // Pass 2: Evaluate all static field initializers (now all fields are known)
  // Create a child environment that can shadow with evaluated field values.
  //
  // Bug-43 / forward-class-reference FIX: evaluating `static const`
  // initializers can require other classes (and their constructors) that
  // appear later in source order. We support deferring this whole block
  // to after every class has been visited.
  final staticFieldEvalEnv = Environment(enclosing: staticInitEnv);

  void evaluateStaticFields() {
    final savedEnvOnEntry = environment;
    try {
      environment = staticFieldEvalEnv;
      for (final variable in staticFieldDecls) {
        final fieldName = variable.name!.name;
        final (isLate, isFinal, initializer) = staticFieldMeta[fieldName]!;

        if (isLate) {
          // Handle late static field
          if (initializer != null) {
            // Late static field with lazy initializer
            final lateVar = LateVariable(fieldName, () {
              // Closure evaluates the initializer on first access.
              final savedEnv = environment;
              try {
                environment = staticFieldEvalEnv;
                return initializer.accept<Object?>(this);
              } finally {
                environment = savedEnv;
              }
            }, isFinal: isFinal);
            klass.staticFields[fieldName] = lateVar;
            // Also define in eval environment for sibling access
            staticFieldEvalEnv.define(fieldName, lateVar);
            Logger.debug(
              "[ClassDecl] Defined late static field '$fieldName' with lazy initializer for class '${klass.name}'.",
            );
          } else {
            // Late static field without initializer
            final lateVar = LateVariable(fieldName, null, isFinal: isFinal);
            klass.staticFields[fieldName] = lateVar;
            staticFieldEvalEnv.define(fieldName, lateVar);
            Logger.debug(
              "[ClassDecl] Defined late static field '$fieldName' without initializer for class '${klass.name}'.",
            );
          }
        } else {
          // Regular static field handling
          Object? value;
          if (initializer != null) {
            value = initializer.accept<Object?>(this);
          }
          klass.staticFields[fieldName] = value;
          // Define the evaluated value in the environment so subsequent fields can access it
          staticFieldEvalEnv.define(fieldName, value);
        }
      }
    } finally {
      environment = savedEnvOnEntry;
    }
  }

  if (deferStaticFieldInits) {
    Logger.debug(
      "[ClassDecl] Deferring static-field initialization for class '${klass.name}' until all classes are declared.",
    );
    _pendingStaticInitializers.add(evaluateStaticFields);
    // Leave environment where the caller expects it (originalVisitorEnv).
    environment = originalVisitorEnv;
  } else {
    try {
      evaluateStaticFields();
    } finally {
      environment = originalVisitorEnv;
    }
  }

  Logger.debug(
    "[Visitor.visitClassDeclaration] Finished processing members for '$className'",
  );

  // Final Checks (run on the populated klass object)
  // Check for unimplemented abstract members
  if (!klass.isAbstract) {
    final inheritedAbstract = klass.getAbstractInheritedMembers();
    // G-DOV-6/7 FIX: Use getAllConcreteMembers() to walk the full superclass chain
    // (not just this class + mixins), so grandparent concrete implementations are found.
    final concreteMembers = klass.getAllConcreteMembers();
    final fieldNames = klass
        .getInstanceFieldNames(); // Fields also satisfy abstract getters
    for (final abstractName in inheritedAbstract.keys) {
      // Check if the abstract member is satisfied by a concrete method/getter/setter OR a field
      if (!concreteMembers.containsKey(abstractName) &&
          !fieldNames.contains(abstractName)) {
        final abstractMember = inheritedAbstract[abstractName]!;
        String memberType = "method";
        if (abstractMember.isGetter) memberType = "getter";
        if (abstractMember.isSetter) memberType = "setter";
        throw RuntimeD4rtException(
          "Missing concrete implementation for inherited abstract $memberType '$abstractName' in class '${klass.name}'.",
        );
      }
    }
  }

  // Check for unimplemented interface members
  if (!klass.isAbstract) {
    final requiredInterfaceMembers = klass.getAllInterfaceMembers();
    final availableConcreteMembers = klass.getAllConcreteMembers();
    for (final requiredName in requiredInterfaceMembers.keys) {
      if (!availableConcreteMembers.containsKey(requiredName)) {
        final memberType = requiredInterfaceMembers[requiredName]!;
        throw RuntimeD4rtException(
          "Missing concrete implementation for interface $memberType '$requiredName' in class '${klass.name}'.",
        );
      }
    }
  }
  // Perf T6: all function tables are fully populated; compact them into
  // their read-only form. staticFields is excluded (deferred init).
  klass.freezeMemberTables();

  Logger.debug("[Visitor.visitClassDeclaration] END for '$className'");
  // No need to define/assign klass in the environment here, it was done in Pass 1.
  return null; // Class declaration statement doesn't return a value
}