rust-lang/rust · critical

Unexpected external source {src:?}

Error message

Unexpected external source {src:?}

What it means

While encoding a span, rustc_metadata checks whether the span's `SourceFile` is imported (foreign). For foreign files it reads `source_file.external_src` expecting the `ExternalSource::Foreign { metadata_index, .. }` variant; any other variant (e.g. `Local`, `Absent`, or not-yet-loaded) triggers `panic!("Unexpected external source {src:?}")`. This invariant — imported file must have a resolved foreign metadata index — is asserted at encoder.rs:319.

Source

Thrown at compiler/rustc_metadata/src/rmeta/encoder.rs:319

        // In `encode_source_map`, we serialize foreign `SourceFile`s into our metadata
        // if we're a proc-macro crate.
        // This allows us to avoid loading the dependencies of proc-macro crates: all of
        // the information we need to decode `Span`s is stored in the proc-macro crate.
        let (kind, metadata_index) = if source_file.is_imported() && !s.is_proc_macro {
            // To simplify deserialization, we 'rebase' this span onto the crate it originally came
            // from (the crate that 'owns' the file it references. These rebased 'lo' and 'hi'
            // values are relative to the source map information for the 'foreign' crate whose
            // CrateNum we write into the metadata. This allows `imported_source_files` to binary
            // search through the 'foreign' crate's source map information, using the
            // deserialized 'lo' and 'hi' values directly.
            //
            // All of this logic ensures that the final result of deserialization is a 'normal'
            // Span that can be used without any additional trouble.
            let metadata_index = {
                // Introduce a new scope so that we drop the 'read()' temporary
                match &*source_file.external_src.read() {
                    ExternalSource::Foreign { metadata_index, .. } => *metadata_index,
                    src => panic!("Unexpected external source {src:?}"),
                }
            };

            (SpanKind::Foreign, metadata_index)
        } else {
            // Record the fact that we need to encode the data for this `SourceFile`
            let source_files =
                s.required_source_files.as_mut().expect("Already encoded SourceMap!");
            let (metadata_index, _) = source_files.insert_full(source_file_index);
            let metadata_index: u32 =
                metadata_index.try_into().expect("cannot export more than U32_MAX files");

            (SpanKind::Local, metadata_index)
        };

        // Encode the start position relative to the file start, so we profit more from the
        // variable-length integer encoding.
        let lo = self.lo - source_file.start_pos;

View on GitHub (pinned to 22057b88b0)

Solutions

  1. If you hit this as a user (not hacking rustc), it is a compiler ICE — run `cargo clean` and rebuild; if it reproduces, file a rustc issue with the `RUST_BACKTRACE=1` output.
  2. For rustc hackers: ensure the foreign `SourceFile`'s `external_src` is populated to `ExternalSource::Foreign { metadata_index, source_map }` before encoding spans, or that `is_proc_macro` correctly routes imported files through the local encode_source_map path.
  3. Audit recent changes to `SourceFile::is_imported` / `encode_source_map` / proc-macro handling to find the missing population of `external_src`.

Example fix

// Conceptual fix in rustc_metadata source-map wiring:
// before: file marked imported but external_src left as Local/Absent
// after:  populate external_src before span encoding
//
// source_file.external_src = RwLock::new(ExternalSource::Foreign {
//     metadata_index,
//     source_map: foreign_source_map.clone(),
// });
Defensive patterns

Strategy: validation

Validate before calling

// The encoder panics on a source kind it doesn't recognise
// ('Unexpected external source'). If you are feeding sources into the encoder
// (e.g. a custom build hook or a proc-macro that emits rmeta), validate the
// source kind against the allow-list BEFORE encoding.
#[derive(Debug)]
enum SourceKind { Crate, Dependency, ExternalCrate /* … */ }

fn is_known_source(src: &SourceKind) -> bool {
    matches!(src, SourceKind::Crate | SourceKind::Dependency
        | SourceKind::ExternalCrate)
}

// Pre-check every source you hand the encoder:
for src in &sources {
    if !is_known_source(src) {
        return Err(format!("unexpected external source {src:?}; refusing to encode"));
    }
 }

Type guard

// Narrow the source to a known variant before passing to the encoder.
enum EncoderSource {
    Local,
    ExternPreloaded,
    ExternMacros,
}

fn classify(src: &SourceKind) -> Option<EncoderSource> {
    match src {
        SourceKind::Crate        => Some(EncoderSource::Local),
        SourceKind::Dependency   => Some(EncoderSource::ExternPreloaded),
        SourceKind::ExternalCrate=> Some(EncoderSource::ExternMacros),
        _ => None,
    }
}

if let Some(kind) = classify(&src) { encoder.encode_source(kind); }

Prevention

When it happens

Trigger: Encoding metadata for a crate whose span references a `SourceFile` flagged `is_imported()` (foreign) but whose `external_src` has not been populated with `ExternalSource::Foreign { .. }` — e.g. the foreign crate's source map was never loaded/encoded, or the proc-macro fast path was taken incorrectly.

Common situations: An internal rustc change that marks a source file imported without wiring up the foreign source map encoding. Building a proc-macro crate where `is_proc_macro` interaction with `is_imported()` misroutes the file into the foreign branch. Span/source-map refactoring that leaves `external_src` in `Local`/`Absent` state at encode time.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/80ac4bbfa39f95e7.json. Report an issue: GitHub.