astral-sh/ruff · error

Calling `find_name_in_mro` on Unknown should return `Some`

Error message

Calling `find_name_in_mro` on Unknown should return `Some`

What it means

The sibling guard of the dynamic case: when a `super()` owner is Divergent, lookup falls back to Type::unknown(), whose MRO lookup is guaranteed to return Some (Unknown degrades to object-level members). The expect fires if the Unknown-type lookup ever returns None.

Source

Thrown at crates/ty_python_semantic/src/types/bound_super.rs:960

                .map(|properties| properties.getters_only(db)),
        ))
    }

    /// Similar to `Type::find_name_in_mro_with_policy`, but performs lookup starting *after* the
    /// pivot class in the MRO, based on the `owner` type instead of the `super` type.
    pub(super) fn find_name_in_mro_after_pivot(
        self,
        db: &'db dyn Db,
        env: &ProgramEnvironment<'db>,
        name: &str,
        policy: MemberLookupPolicy,
    ) -> PlaceAndQualifiers<'db> {
        let owner = self.owner(db);
        let class = match &owner {
            SuperOwnerKind::Dynamic(dynamic) => {
                return Type::Dynamic(*dynamic)
                    .find_name_in_mro_with_policy(db, env, name, policy)
                    .expect("Calling `find_name_in_mro` on dynamic type should return `Some`");
            }
            SuperOwnerKind::Divergent(_) => {
                return Type::unknown()
                    .find_name_in_mro_with_policy(db, env, name, policy)
                    .expect("Calling `find_name_in_mro` on Unknown should return `Some`");
            }
            SuperOwnerKind::Resolved(resolved_owner) => resolved_owner.lookup_anchor,
        };

        let mut mro_after_pivot = self.skip_until_after_pivot(db, env, owner.iter_mro(db, env));
        let class_literal = class.class_literal(db);
        let result =
            class_literal.class_member_from_mro(db, env, name, policy, mro_after_pivot.clone());

        // TODO: Here we are hard-coding that __class_getitem__ is the only member defined in
        // typing._Generic in the typeshed, and we are hard-coding its signature. Ideally we would
        // look that up from the typeshed class, but that would require threading through the
        // static class literal through the SpecialForm and KnownInstance types that we create.

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. File a ty issue with the snippet (super() under divergent narrowing) and the backtrace
  2. As a contributor: keep the Some(undefined place) contract for Unknown in find_name_in_mro_with_policy; add a divergent-owner super() test
  3. Reproduce under the latest ty build to check whether a recent lookup refactor already fixed it
Defensive patterns

Strategy: fallback

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check_file(&db, file)));
match result {
    Ok(diags) => diags,
    Err(payload) => { log::warn!("ty panicked in super() lookup (divergent owner) on {}: {:?}", file, payload); vec![] }
}

Prevention

When it happens

Trigger: `super().attr` where the owner resolved to a divergent type (e.g., after narrowing in possibly-unreachable code) and the Unknown MRO lookup returns None - an invariant break in the base lookup path, not something the Python source controls directly.

Common situations: Same class of regression as the dynamic case: member-lookup policy changes or early None returns; typically hit in narrowing-heavy code or via fuzzers.

Related errors


AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20). Data as JSON: /api/errors/98fb1d23e8f0c712. Report an issue: GitHub.