Repository: astral-sh/ruff
File: crates/ty_python_semantic/src/types/narrow.rs (Rust)

Code before:
                    // double negative, i.e. "you're *not* a `TypedDict` *without* this literal
                    // field". As the first step of building that, we negate the right hand side.
                    (ast::CmpOp::Eq, true) | (ast::CmpOp::NotEq, false) => {
                        IntersectionBuilder::new(self.db)
                            .add_negative(rhs_type)
                            .build()
                    }
                    // The opposite, i.e. "you're *not* a `TypedDict` *with* this literal field".
                    // Don't negate the right hand side.
                    (ast::CmpOp::Eq, false) | (ast::CmpOp::NotEq, true) => rhs_type,
                    _ => unreachable!(),
                };
                // Create the synthesized `TypedDict` with that (possibly negated) field. We don't
                // want to constrain the mutability or required-ness of the field, so the most
                // compatible form is not-required and read-only.
                let field = TypedDictFieldBuilder::new(field_type)
                    .required(false)
                    .read_only(true)
                    .build();
                let schema: TypedDictSchema = std::iter::once((field_name, field)).collect();
                let synthesized_typeddict =
                    TypedDictType::Synthesized(SynthesizedTypedDictType::new(self.db, schema));
                // As mentioned above, the synthesized `TypedDict` is always negated.
                let intersection = IntersectionBuilder::new(self.db)
                    .add_negative(Type::TypedDict(synthesized_typeddict))
                    .build();
                let place = self.expect_place(&subscript_place_expr);
                constraints.insert(place, intersection);
            }
        }

        let mut last_rhs_ty: Option<Type> = None;

        for (op, (left, right)) in std::iter::zip(&**ops, comparator_tuples) {
            let lhs_ty = last_rhs_ty.unwrap_or_else(|| inference.expression_type(left));
            let rhs_ty = inference.expression_type(right);
            last_rhs_ty = Some(rhs_ty);

            match left {
                ast::Expr::Name(_)
                | ast::Expr::Attribute(_)
                | ast::Expr::Subscript(_)
                | ast::Expr::Named(_) => {
                    if let Some(left) = place_expr(left)
                        && let Some(ty) =
                            self.evaluate_expr_compare_op(lhs_ty, rhs_ty, *op, is_positive)
                    {
                        let place = self.expect_place(&left);
                        constraints.insert(place, ty);
                    }
                }

Review comment (suggestion):
```suggestion
                let intersection = Type::TypedDict(synthesized_typeddict).negate(self.db);
```

Code after:
                // compatible form is not-required and read-only.
                let field = TypedDictFieldBuilder::new(field_type)
                    .required(false)
                    .read_only(true)
                    .build();
                let schema = TypedDictSchema::from_iter([(field_name, field)]);
                let synthesized_typeddict =
                    TypedDictType::Synthesized(SynthesizedTypedDictType::new(self.db, schema));
                // As mentioned above, the synthesized `TypedDict` is always negated.
                let intersection = Type::TypedDict(synthesized_typeddict).negate(self.db);
                let place = self.expect_place(&subscript_place_expr);
                constraints.insert(place, intersection);
            }
        }

        let mut last_rhs_ty: Option<Type> = None;

        for (op, (left, right)) in std::iter::zip(&**ops, comparator_tuples) {
            let lhs_ty = last_rhs_ty.unwrap_or_else(|| inference.expression_type(left));
            let rhs_ty = inference.expression_type(right);
            last_rhs_ty = Some(rhs_ty);

            match left {
                ast::Expr::Name(_)
                | ast::Expr::Attribute(_)
                | ast::Expr::Subscript(_)
                | ast::Expr::Named(_) => {
                    if let Some(left) = place_expr(left)
                        && let Some(ty) =
                            self.evaluate_expr_compare_op(lhs_ty, rhs_ty, *op, is_positive)
                    {
                        let place = self.expect_place(&left);
                        constraints.insert(place, ty);
                    }
                }
                ast::Expr::Call(ast::ExprCall {
                    range: _,
                    node_index: _,
                    func: callable,
                    arguments:
                        ast::Arguments {
                            args,
                            keywords,
                            range: _,
                            node_index: _,
                        },
                }) if keywords.is_empty() => {
                    let Type::ClassLiteral(rhs_class) = rhs_ty else {
                        continue;
                    };