astral-sh/ruff · error

DynamicClassAnchor::Definition should only be used for assig

Error message

DynamicClassAnchor::Definition should only be used for assignments

What it means

For an assigned dynamic-class call (`C = make_class('C', bases)`), ty defers reading the explicit base classes until needed. `deferred_explicit_bases` re-reads the definition's value node from the parsed module; the expect asserts the definition is an assignment with a value. It fires when a `DynamicClassAnchor::Definition` was created for a definition whose kind yields no value expression.

Source

Thrown at crates/ty_python_semantic/src/types/class/dynamic_literal.rs:213

    ///
    /// Returns `[Unknown]` if the bases iterable is variable-length.
    pub(crate) fn explicit_bases(self, db: &'db dyn Db) -> &'db [Type<'db>] {
        /// Inner cached function for deferred inference of bases.
        /// Only called for assigned calls where inference was deferred.
        #[salsa::tracked(returns(deref), cycle_initial=|_, _, _| Box::default(), heap_size=ruff_memory_usage::heap_size)]
        fn deferred_explicit_bases<'db>(
            db: &'db dyn Db,
            definition: Definition<'db>,
        ) -> Box<[Type<'db>]> {
            let program_file = definition.program_file(db);
            let python_file = program_file.python_file(db);
            let env = ProgramEnvironment::from_file(program_file);
            let module = parsed_module(db, python_file).load(db);

            let value = definition
                .kind(db)
                .value(&module)
                .expect("DynamicClassAnchor::Definition should only be used for assignments");
            let call_expr = value
                .as_call_expr()
                .expect("Definition value should be a call expression");

            let Some(bases_arg) = dynamic_class_bases_argument(&call_expr.arguments) else {
                return Box::default();
            };

            // Use `definition_expression_type` for deferred inference support.
            extract_fixed_length_iterable_element_types(db, &env, bases_arg, |expr| {
                definition_expression_type(db, definition, expr)
            })
            .unwrap_or_else(|| Box::from([Type::unknown()]))
        }

        match self.anchor(db) {
            // For dangling calls, bases are stored directly on the anchor.
            DynamicClassAnchor::ScopeOffset { explicit_bases, .. } => explicit_bases.as_ref(),

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. At the site that builds `DynamicClassAnchor::Definition`, only anchor definitions for which `.kind(db).value(&module)` is Some.
  2. Return empty bases (as the `dynamic_class_bases_argument` None path already does) instead of anchoring unmatched definitions.
  3. Add an mdtest for the offending assignment shape and run the ty_python_semantic suite.
  4. File an issue with the reproducer if it happens on stock ty.

Example fix

// before
DynamicClassAnchor::Definition(definition)

// after: guard at construction, degrade to no explicit bases
let value = definition.kind(db).value(&module);
let anchor = value.is_some().then(|| DynamicClassAnchor::Definition(definition));
Defensive patterns

Strategy: try-catch

Try / catch

let bases = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    dynamic_class.explicit_bases(db)
}))
.unwrap_or_else(|_| Box::from([Type::unknown()])); // degrade to unknown bases

Prevention

When it happens

Trigger: Calling `explicit_bases`/base resolution on a dynamic class anchored via `DynamicClassAnchor::Definition` (dynamic_literal.rs:233) where `definition.kind(db).value(&module)` is None - a non-assignment definition, or a definition resolved against a different module parse.

Common situations: Extending dynamic-class support to new constructs (module-level `type(...)` statements, conditional assignments, aliased targets) and anchoring definitions that are not plain assignments; refactors of definition kinds; anchors resolved after the file was reparsed without invalidation.

Related errors


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