astral-sh/ruff · error

Definition value should be a call expression

Error message

Definition value should be a call expression

What it means

After asserting the definition is an assignment (error 145), `deferred_explicit_bases` further asserts its value is a call expression, since dynamic classes are created by calls like `make_class('C', ...)` or `Enum('Color', 'RED GREEN')`. The `as_call_expr().expect(...)` fires when the stored value node is not an `ast::ExprCall`.

Source

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

        /// 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(),
            // For assigned calls, use deferred inference.
            DynamicClassAnchor::Definition(definition) => deferred_explicit_bases(db, *definition),
        }

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Only build the Definition anchor after matching the RHS as a call expression with the recognized callee.
  2. When the value is not a call, treat the class as having no explicit bases rather than anchoring it.
  3. Cover the new construct with an mdtest; run `cargo nextest run -p ty_python_semantic`.
  4. Report upstream with a minimal file if reachable on an unmodified build.

Example fix

// before
let call_expr = value.as_call_expr().expect("Definition value should be a call expression");

// after: match instead of expect
let Some(call_expr) = value.as_call_expr() else {
    return Box::default();
};
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()]));

Prevention

When it happens

Trigger: Resolving explicit bases for a Definition-anchored dynamic class whose definition value is not a call expression - e.g. the anchor was built before confirming the RHS is a recognized dynamic-class call, or the definition was rebound (`C = C_or_something_else`) between anchoring and resolution.

Common situations: Contributions broadening dynamic-class detection beyond call RHS patterns; changes to how assigned names are re-bound (multiple assignment targets, `C = D = make_class(...)`); stale anchors after reparse.

Related errors


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