importEnvironment method

void importEnvironment(
  1. Environment other, {
  2. Set<String>? show,
  3. Set<String>? hide,
  4. bool errorOnConflict = false,
})

Imports definitions from another environment into this one. Can be filtered using show or hide combinators. If no filter is provided, all symbols from other are merged. This method directly modifies the current environment.

When errorOnConflict is true (used by export directives merging into a library's exported environment — see Cluster EXPORT, I-MISC-40/41), duplicate symbols with non-identical definitions raise a RuntimeD4rtException('Name conflict in environment: ...') instead of silently overwriting. The default false preserves the import-wins semantics expected of regular import directives.

Implementation

void importEnvironment(
  Environment other, {
  Set<String>? show,
  Set<String>? hide,
  bool errorOnConflict = false,
}) {
  if (show != null && hide != null) {
    throw ArgumentD4rtException(
      'Cannot provide both show and hide to importEnvironment.',
    );
  }

  Environment sourceEnvToImportFrom;

  if (show != null || hide != null) {
    sourceEnvToImportFrom = other.shallowCopyFiltered(
      showNames: show,
      hideNames: hide,
    );
    Logger.debug(
      "[Environment.importEnvironment] Importing from a filtered version of other env (hashCode: ${other.hashCode}).",
    );
  } else {
    sourceEnvToImportFrom = other;
    Logger.debug(
      "[Environment.importEnvironment] Importing directly from other env (hashCode: ${other.hashCode}).",
    );
  }

  // Perform the merge from sourceEnvToImportFrom
  sourceEnvToImportFrom._values.forEach((name, value) {
    if (_values.containsKey(name)) {
      // Allow if it's the same value (e.g., same class/function imported via different paths)
      if (!identical(_values[name], value)) {
        if (errorOnConflict) {
          // Cluster EXPORT (I-MISC-40/41): export-merge cannot silently
          // overwrite — a library that re-publishes two different
          // definitions of the same name is malformed.
          throw RuntimeD4rtException(
            "Name conflict in environment: Symbol '$name' is already defined.",
          );
        }
        // GEN-100 FIX: Import wins for value conflicts too.
        // This handles cases where a pre-registered function or variable
        // is overwritten by an explicit import.
        Logger.debug(
          "[Environment.importEnvironment] GEN-100: Overwriting pre-registered "
          "value '$name' with imported version",
        );
        _values[name] = value;
      }
      // Same value, skip the duplicate
      return;
    }
    if (_bridgedClasses.containsKey(name) ||
        _bridgedEnums.containsKey(name) ||
        _prefixedImports.containsKey(name)) {
      if (errorOnConflict) {
        throw RuntimeD4rtException(
          "Name conflict in environment: Symbol '$name' is already defined.",
        );
      }
      // GEN-100 FIX: Import values can override pre-registered bridged
      // types when a library re-exports something with the same name.
      Logger.debug(
        "[Environment.importEnvironment] GEN-100: Import value '$name' "
        "replaces pre-registered type definition",
      );
    }
    _values[name] = value;
  });

  sourceEnvToImportFrom._bridgedClasses.forEach((name, bridgedClass) {
    if (_bridgedClasses.containsKey(name)) {
      // Allow if it's the same bridged class (identity check)
      if (identical(_bridgedClasses[name], bridgedClass)) {
        return;
      }
      if (errorOnConflict) {
        throw RuntimeD4rtException(
          "Name conflict in environment: Symbol '$name' is already defined.",
        );
      }
      // GEN-100 FIX: When a bridged class with the same name but different
      // definition exists (e.g., dart:ui.TextStyle pre-registered vs
      // painting.TextStyle from an explicit import), the IMPORT WINS.
      // This matches Dart's import semantics: explicit imports take priority
      // over pre-registered (ambient) definitions.
      Logger.debug(
        "[Environment.importEnvironment] GEN-100: Overwriting pre-registered "
        "bridged class '$name' with imported version",
      );
      _bridgedClassesOrNew[name] = bridgedClass;
      _bridgedClassesLookupByTypeOrNew[bridgedClass.nativeType] = bridgedClass;
      _invalidateResolutionCache();
      return;
    }
    if (_values.containsKey(name) ||
        _bridgedEnums.containsKey(name) ||
        _prefixedImports.containsKey(name)) {
      if (errorOnConflict) {
        throw RuntimeD4rtException(
          "Name conflict in environment: Symbol '$name' is already defined.",
        );
      }
      // Cluster A fix: a local script-level declaration (interpreted enum,
      // class, function, or top-level variable) shadows an imported bridged
      // class with the same name. Per Dart import semantics local
      // declarations always win over imports for unprefixed names — silently
      // skip the import. Type-based lookups still find the bridge via the
      // global _bridgedClassesLookupByType map.
      Logger.debug(
        "[Environment.importEnvironment] Local declaration of '$name' "
        "shadows imported bridged class — skipping import",
      );
      return;
    }
    _bridgedClassesOrNew[name] = bridgedClass;
    _bridgedClassesLookupByTypeOrNew[bridgedClass.nativeType] = bridgedClass;
    _invalidateResolutionCache();
  });

  sourceEnvToImportFrom._bridgedEnums.forEach((name, bridgedEnum) {
    if (_bridgedEnums.containsKey(name)) {
      // Allow if it's the same bridged enum (identity check)
      if (identical(_bridgedEnums[name], bridgedEnum)) {
        return;
      }
      if (errorOnConflict) {
        throw RuntimeD4rtException(
          "Name conflict in environment: Symbol '$name' is already defined.",
        );
      }
      // GEN-100 FIX: Import wins for enum conflicts too.
      Logger.debug(
        "[Environment.importEnvironment] GEN-100: Overwriting pre-registered "
        "bridged enum '$name' with imported version",
      );
      _bridgedEnumsOrNew[name] = bridgedEnum;
      return;
    }
    if (_values.containsKey(name) ||
        _bridgedClasses.containsKey(name) ||
        _prefixedImports.containsKey(name)) {
      if (errorOnConflict) {
        throw RuntimeD4rtException(
          "Name conflict in environment: Symbol '$name' is already defined.",
        );
      }
      // Cluster A fix: local declaration shadows imported bridged enum.
      // See comment on the bridged-class branch above.
      Logger.debug(
        "[Environment.importEnvironment] Local declaration of '$name' "
        "shadows imported bridged enum — skipping import",
      );
      return;
    }
    _bridgedEnumsOrNew[name] = bridgedEnum;
  });

  sourceEnvToImportFrom._prefixedImports.forEach((name, env) {
    if (_prefixedImports.containsKey(name)) {
      // Same identity — nothing to do.
      if (identical(_prefixedImports[name], env)) {
        return;
      }
      // Different env objects bound to the same prefix. This is legal in
      // Dart: multiple files (or even a single file) may declare imports
      // with the same prefix, and the prefix scope is additive over all of
      // them (`import 'dart:math' as m;` and `import 'package:foo' as m;`
      // both expose names via `m.`). In our module loader the per-file
      // module environments each create their own `shallowCopyFiltered`
      // copy of the imported env, so even imports of the *same library*
      // under the same prefix produce non-identical env objects in two
      // different files. Merge the contents instead of throwing.
      _prefixedImports[name]!.importEnvironment(env, errorOnConflict: false);
      return;
    }
    if (_values.containsKey(name) ||
        _bridgedClasses.containsKey(name) ||
        _bridgedEnums.containsKey(name)) {
      throw RuntimeD4rtException(
        "Name conflict in environment: Symbol '$name' (prefixed import) is already defined or collides with another symbol type.",
      );
    }
    _prefixedImportsOrNew[name] = env;
  });

  // Unnamed extensions are additive. C11: Guard against the self-import /
  // shared-reference case where `this` and `sourceEnvToImportFrom` share the
  // same `_unnamedExtensions` list (observed in the foundation/
  // synchronousfuture_test corpus: the module loader returns the importing
  // environment itself for a transitive import, so `addAll(sameList)`
  // iterates the list while mutating it and throws
  // `ConcurrentModificationError`). Skipping the merge is correct: the
  // destination already contains every element of the source.
  if (!identical(
    _unnamedExtensions,
    sourceEnvToImportFrom._unnamedExtensions,
  )) {
    _unnamedExtensionsOrNew.addAll(sourceEnvToImportFrom._unnamedExtensions);
  }

  Logger.debug(
    "[Environment.importEnvironment] Merge complete. Current env (hashCode: $hashCode) updated.",
  );
}