astral-sh/ruff · error

Calling `find_name_in_mro` on dynamic type should return `So

Error message

Calling `find_name_in_mro` on dynamic type should return `Some`

What it means

In `super()` member lookup (find_name_in_mro_after_pivot), when the super owner is dynamic (Any/Unknown), lookup delegates to Type::Dynamic::find_name_in_mro_with_policy, which is guaranteed to return Some because dynamic types fall back to object-level members with an undefined place. The expect enforces that guarantee.

Source

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

            descriptor_error.map(MemberLookupErrorKind::DescriptorGet),
            instance
                .and_then(|_| attribute.place.ignore_possibly_undefined())
                .and_then(|ty| ty.property_deprecations(db))
                // `super` delegates reads to the owner's descriptors, but not writes or deletions.
                .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());

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. File a ty issue with the `super()` expression, the member name, and the backtrace
  2. As a contributor: audit find_name_in_mro_with_policy early-return paths; they must still yield Some with an undefined place for dynamic types
  3. Add a regression test covering `super().<member>` in a class with an untyped/dynamic base
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 on {}: {:?}", file, payload); vec![] }
}

Prevention

When it happens

Trigger: `super().attr` where the enclosing class resolves to a dynamic type, and the dynamic MRO lookup returns None - an internal regression in the dynamic lookup path (early returns or name filtering) rather than a property of the user's Python input.

Common situations: Refactors of find_name_in_mro or MemberLookupPolicy that introduce early None returns (e.g., filtered member names) without preserving the dynamic fallback; usually surfaced by fuzzing or large codebases with untyped classes.

Related errors


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