Merge remote-tracking branch 'origin/main' into dcreager/callable-return
* origin/main: new module for parsing ranged suppressions (#21441) [ty] `type[T]` is assignable to an inferable typevar (#21766) Fix syntax error false positives for `await` outside functions (#21763) [ty] Improve diagnostics for unsupported comparison operations (#21737)
This commit is contained in:
@@ -2845,6 +2845,11 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||
match scope.kind() {
|
||||
ScopeKind::Class => return false,
|
||||
ScopeKind::Function | ScopeKind::Lambda => return true,
|
||||
ScopeKind::Comprehension
|
||||
if matches!(scope.node(), NodeWithScopeKind::GeneratorExpression(_)) =>
|
||||
{
|
||||
return true;
|
||||
}
|
||||
ScopeKind::Comprehension
|
||||
| ScopeKind::Module
|
||||
| ScopeKind::TypeAlias
|
||||
@@ -2894,11 +2899,14 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||
matches!(kind, ScopeKind::Function | ScopeKind::Lambda)
|
||||
}
|
||||
|
||||
fn in_generator_scope(&self) -> bool {
|
||||
matches!(
|
||||
self.scopes[self.current_scope()].node(),
|
||||
NodeWithScopeKind::GeneratorExpression(_)
|
||||
)
|
||||
fn in_generator_context(&self) -> bool {
|
||||
for scope_info in &self.scope_stack {
|
||||
let scope = &self.scopes[scope_info.file_scope_id];
|
||||
if matches!(scope.node(), NodeWithScopeKind::GeneratorExpression(_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn in_notebook(&self) -> bool {
|
||||
|
||||
@@ -2134,33 +2134,52 @@ impl<'db> Type<'db> {
|
||||
})
|
||||
.is_never_satisfied(db) =>
|
||||
{
|
||||
// TODO: The repetition here isn't great, but we really need the fallthrough logic,
|
||||
// where this arm only engages if it returns true.
|
||||
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||
target.to_instance(db).when_some_and(|other_instance| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
// TODO: The repetition here isn't great, but we need the fallthrough logic.
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(target.to_instance(db))
|
||||
.when_some_and(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
(_, Type::SubclassOf(subclass_of)) if subclass_of.is_type_var() => {
|
||||
let other_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||
self.to_instance(db).when_some_and(|this_instance| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
other_instance,
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
(_, Type::SubclassOf(subclass_of))
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(self.to_instance(db))
|
||||
.when_some_and(|(other_instance, this_instance)| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
Type::TypeVar(other_instance),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
.is_never_satisfied(db) =>
|
||||
{
|
||||
// TODO: The repetition here isn't great, but we need the fallthrough logic.
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(self.to_instance(db))
|
||||
.when_some_and(|(other_instance, this_instance)| {
|
||||
this_instance.has_relation_to_impl(
|
||||
db,
|
||||
Type::TypeVar(other_instance),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// A fully static typevar is a subtype of its upper bound, and to something similar to
|
||||
@@ -2690,7 +2709,9 @@ impl<'db> Type<'db> {
|
||||
disjointness_visitor,
|
||||
),
|
||||
|
||||
(Type::SubclassOf(subclass_of), _) if subclass_of.is_type_var() => {
|
||||
(Type::SubclassOf(subclass_of), _) | (_, Type::SubclassOf(subclass_of))
|
||||
if subclass_of.is_type_var() =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
@@ -3150,33 +3171,33 @@ impl<'db> Type<'db> {
|
||||
|
||||
// `type[T]` is disjoint from a class object `A` if every instance of `T` is disjoint from an instance of `A`.
|
||||
(Type::SubclassOf(subclass_of), other) | (other, Type::SubclassOf(subclass_of))
|
||||
if subclass_of.is_type_var()
|
||||
&& (other.to_instance(db).is_some()
|
||||
|| other.as_typevar().is_some_and(|type_var| {
|
||||
type_var.typevar(db).bound_or_constraints(db).is_none()
|
||||
})) =>
|
||||
if !subclass_of
|
||||
.into_type_var()
|
||||
.zip(other.to_instance(db))
|
||||
.when_none_or(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
.is_always_satisfied(db) =>
|
||||
{
|
||||
let this_instance = Type::TypeVar(subclass_of.into_type_var().unwrap());
|
||||
let other_instance = match other {
|
||||
// An unbounded typevar `U` may have instances of type `object` if specialized to
|
||||
// an instance of `type`.
|
||||
Type::TypeVar(typevar)
|
||||
if typevar.typevar(db).bound_or_constraints(db).is_none() =>
|
||||
{
|
||||
Some(Type::object())
|
||||
}
|
||||
_ => other.to_instance(db),
|
||||
};
|
||||
|
||||
other_instance.when_none_or(|other_instance| {
|
||||
this_instance.is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
// TODO: The repetition here isn't great, but we need the fallthrough logic.
|
||||
subclass_of
|
||||
.into_type_var()
|
||||
.zip(other.to_instance(db))
|
||||
.when_none_or(|(this_instance, other_instance)| {
|
||||
Type::TypeVar(this_instance).is_disjoint_from_impl(
|
||||
db,
|
||||
other_instance,
|
||||
inferable,
|
||||
disjointness_visitor,
|
||||
relation_visitor,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// A typevar is never disjoint from itself, since all occurrences of the typevar must
|
||||
|
||||
@@ -18,12 +18,14 @@ use crate::types::class::{
|
||||
CodeGeneratorKind, DisjointBase, DisjointBaseKind, Field, MethodDecorator,
|
||||
};
|
||||
use crate::types::function::{FunctionDecorators, FunctionType, KnownFunction, OverloadLiteral};
|
||||
use crate::types::infer::UnsupportedComparisonError;
|
||||
use crate::types::overrides::MethodKind;
|
||||
use crate::types::string_annotation::{
|
||||
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
||||
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
||||
RAW_STRING_TYPE_ANNOTATION,
|
||||
};
|
||||
use crate::types::tuple::TupleSpec;
|
||||
use crate::types::{
|
||||
BoundTypeVarInstance, ClassType, DynamicType, LintDiagnosticGuard, Protocol,
|
||||
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
|
||||
@@ -2410,7 +2412,7 @@ pub(super) fn report_invalid_assignment<'db>(
|
||||
}
|
||||
|
||||
let settings =
|
||||
DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), target_ty, value_ty);
|
||||
DisplaySettings::from_possibly_ambiguous_types(context.db(), [target_ty, value_ty]);
|
||||
|
||||
let diagnostic_range = if let Some(value_node) = value_node {
|
||||
// Expand the range to include parentheses around the value, if any. This allows
|
||||
@@ -2548,7 +2550,7 @@ pub(super) fn report_invalid_return_type(
|
||||
};
|
||||
|
||||
let settings =
|
||||
DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), expected_ty, actual_ty);
|
||||
DisplaySettings::from_possibly_ambiguous_types(context.db(), [expected_ty, actual_ty]);
|
||||
let return_type_span = context.span(return_type_range);
|
||||
|
||||
let mut diag = builder.into_diagnostic("Return type does not match returned value");
|
||||
@@ -4054,6 +4056,112 @@ pub(super) fn report_overridden_final_method<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_unsupported_comparison<'db>(
|
||||
context: &InferContext<'db, '_>,
|
||||
error: &UnsupportedComparisonError<'db>,
|
||||
range: TextRange,
|
||||
left: &ast::Expr,
|
||||
right: &ast::Expr,
|
||||
left_ty: Type<'db>,
|
||||
right_ty: Type<'db>,
|
||||
) {
|
||||
let db = context.db();
|
||||
|
||||
let Some(diagnostic_builder) = context.report_lint(&UNSUPPORTED_OPERATOR, range) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let display_settings = DisplaySettings::from_possibly_ambiguous_types(
|
||||
db,
|
||||
[error.left_ty, error.right_ty, left_ty, right_ty],
|
||||
);
|
||||
|
||||
let mut diagnostic =
|
||||
diagnostic_builder.into_diagnostic(format_args!("Unsupported `{}` operation", error.op));
|
||||
|
||||
if left_ty == right_ty {
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Both operands have type `{}`",
|
||||
left_ty.display_with(db, display_settings.clone())
|
||||
));
|
||||
diagnostic.annotate(context.secondary(left));
|
||||
diagnostic.annotate(context.secondary(right));
|
||||
diagnostic.set_concise_message(format_args!(
|
||||
"Operator `{}` is not supported between two objects of type `{}`",
|
||||
error.op,
|
||||
left_ty.display_with(db, display_settings.clone())
|
||||
));
|
||||
} else {
|
||||
for (ty, expr) in [(left_ty, left), (right_ty, right)] {
|
||||
diagnostic.annotate(context.secondary(expr).message(format_args!(
|
||||
"Has type `{}`",
|
||||
ty.display_with(db, display_settings.clone())
|
||||
)));
|
||||
}
|
||||
diagnostic.set_concise_message(format_args!(
|
||||
"Operator `{}` is not supported between objects of type `{}` and `{}`",
|
||||
error.op,
|
||||
left_ty.display_with(db, display_settings.clone()),
|
||||
right_ty.display_with(db, display_settings.clone())
|
||||
));
|
||||
}
|
||||
|
||||
// For non-atomic types like unions and tuples, we now provide context
|
||||
// on the underlying elements that caused the error.
|
||||
// If we're emitting a diagnostic for something like `(1, "foo") < (2, 3)`:
|
||||
//
|
||||
// - `left_ty` is `tuple[Literal[1], Literal["foo"]]`
|
||||
// - `right_ty` is `tuple[Literal[2], Literal[3]]
|
||||
// - `error.left_ty` is `Literal["foo"]`
|
||||
// - `error.right_ty` is `Literal[3]`
|
||||
if (error.left_ty, error.right_ty) != (left_ty, right_ty) {
|
||||
if let Some(TupleSpec::Fixed(lhs_spec)) = left_ty.tuple_instance_spec(db).as_deref()
|
||||
&& let Some(TupleSpec::Fixed(rhs_spec)) = right_ty.tuple_instance_spec(db).as_deref()
|
||||
&& lhs_spec.len() == rhs_spec.len()
|
||||
&& let Some(position) = lhs_spec
|
||||
.elements()
|
||||
.zip(rhs_spec.elements())
|
||||
.position(|tup| tup == (&error.left_ty, &error.right_ty))
|
||||
{
|
||||
if error.left_ty == error.right_ty {
|
||||
diagnostic.info(format_args!(
|
||||
"Operation fails because operator `{}` is not supported between \
|
||||
the tuple elements at index {} (both of type `{}`)",
|
||||
error.op,
|
||||
position + 1,
|
||||
error.left_ty.display_with(db, display_settings),
|
||||
));
|
||||
} else {
|
||||
diagnostic.info(format_args!(
|
||||
"Operation fails because operator `{}` is not supported between \
|
||||
the tuple elements at index {} (of type `{}` and `{}`)",
|
||||
error.op,
|
||||
position + 1,
|
||||
error.left_ty.display_with(db, display_settings.clone()),
|
||||
error.right_ty.display_with(db, display_settings),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if error.left_ty == error.right_ty {
|
||||
diagnostic.info(format_args!(
|
||||
"Operation fails because operator `{}` is not supported \
|
||||
between two objects of type `{}`",
|
||||
error.op,
|
||||
error.left_ty.display_with(db, display_settings),
|
||||
));
|
||||
} else {
|
||||
diagnostic.info(format_args!(
|
||||
"Operation fails because operator `{}` is not supported \
|
||||
between objects of type `{}` and `{}`",
|
||||
error.op,
|
||||
error.left_ty.display_with(db, display_settings.clone()),
|
||||
error.right_ty.display_with(db, display_settings)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function receives an unresolved `from foo import bar` import,
|
||||
/// where `foo` can be resolved to a module but that module does not
|
||||
/// have a `bar` member or submodule.
|
||||
|
||||
@@ -72,14 +72,15 @@ impl<'db> DisplaySettings<'db> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_possibly_ambiguous_type_pair(
|
||||
pub fn from_possibly_ambiguous_types(
|
||||
db: &'db dyn Db,
|
||||
type_1: Type<'db>,
|
||||
type_2: Type<'db>,
|
||||
types: impl IntoIterator<Item = Type<'db>>,
|
||||
) -> Self {
|
||||
let collector = AmbiguousClassCollector::default();
|
||||
collector.visit_type(db, type_1);
|
||||
collector.visit_type(db, type_2);
|
||||
|
||||
for ty in types {
|
||||
collector.visit_type(db, ty);
|
||||
}
|
||||
|
||||
Self {
|
||||
qualified: Rc::new(
|
||||
|
||||
@@ -57,6 +57,7 @@ use crate::types::{
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
use builder::TypeInferenceBuilder;
|
||||
pub(super) use builder::UnsupportedComparisonError;
|
||||
|
||||
mod builder;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -79,7 +79,7 @@ use crate::types::diagnostic::{
|
||||
report_invalid_type_checking_constant, report_named_tuple_field_with_leading_underscore,
|
||||
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
||||
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
||||
report_rebound_typevar, report_slice_step_size_zero,
|
||||
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_comparison,
|
||||
};
|
||||
use crate::types::function::{
|
||||
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
|
||||
@@ -158,7 +158,7 @@ impl<'db> DeclaredAndInferredType<'db> {
|
||||
type BinaryComparisonVisitor<'db> = CycleDetector<
|
||||
ast::CmpOp,
|
||||
(Type<'db>, ast::CmpOp, Type<'db>),
|
||||
Result<Type<'db>, CompareUnsupportedError<'db>>,
|
||||
Result<Type<'db>, UnsupportedComparisonError<'db>>,
|
||||
>;
|
||||
|
||||
/// We currently store one dataclass field-specifiers inline, because that covers standard
|
||||
@@ -10056,26 +10056,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
&BinaryComparisonVisitor::new(Ok(Type::BooleanLiteral(true))),
|
||||
)
|
||||
.unwrap_or_else(|error| {
|
||||
if let Some(diagnostic_builder) =
|
||||
builder.context.report_lint(&UNSUPPORTED_OPERATOR, range)
|
||||
{
|
||||
// Handle unsupported operators (diagnostic, `bool`/`Unknown` outcome)
|
||||
diagnostic_builder.into_diagnostic(format_args!(
|
||||
"Operator `{}` is not supported for types `{}` and `{}`{}",
|
||||
error.op,
|
||||
error.left_ty.display(builder.db()),
|
||||
error.right_ty.display(builder.db()),
|
||||
if (left_ty, right_ty) == (error.left_ty, error.right_ty) {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
", in comparing `{}` with `{}`",
|
||||
left_ty.display(builder.db()),
|
||||
right_ty.display(builder.db())
|
||||
)
|
||||
}
|
||||
));
|
||||
}
|
||||
report_unsupported_comparison(
|
||||
&builder.context,
|
||||
&error,
|
||||
range,
|
||||
left,
|
||||
right,
|
||||
left_ty,
|
||||
right_ty,
|
||||
);
|
||||
|
||||
match op {
|
||||
// `in, not in, is, is not` always return bool instances
|
||||
@@ -10101,13 +10090,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
intersection_on: IntersectionOn,
|
||||
range: TextRange,
|
||||
visitor: &BinaryComparisonVisitor<'db>,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
) -> Result<Type<'db>, UnsupportedComparisonError<'db>> {
|
||||
enum State<'db> {
|
||||
// We have not seen any positive elements (yet)
|
||||
NoPositiveElements,
|
||||
// The operator was unsupported on all elements that we have seen so far.
|
||||
// Contains the first error we encountered.
|
||||
UnsupportedOnAllElements(CompareUnsupportedError<'db>),
|
||||
UnsupportedOnAllElements(UnsupportedComparisonError<'db>),
|
||||
// The operator was supported on at least one positive element.
|
||||
Supported,
|
||||
}
|
||||
@@ -10263,7 +10252,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right: Type<'db>,
|
||||
range: TextRange,
|
||||
visitor: &BinaryComparisonVisitor<'db>,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
) -> Result<Type<'db>, UnsupportedComparisonError<'db>> {
|
||||
// Note: identity (is, is not) for equal builtin types is unreliable and not part of the
|
||||
// language spec.
|
||||
// - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal
|
||||
@@ -10335,7 +10324,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
IntersectionOn::Left,
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|err|UnsupportedComparisonError { op, left_ty: left, right_ty: err.right_ty }))
|
||||
}
|
||||
(left, Type::Intersection(intersection)) => {
|
||||
Some(self.infer_binary_intersection_type_comparison(
|
||||
@@ -10345,7 +10334,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
IntersectionOn::Right,
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|err|UnsupportedComparisonError { op, left_ty: err.left_ty, right_ty: right }))
|
||||
}
|
||||
|
||||
(Type::TypeAlias(alias), right) => Some(
|
||||
@@ -10392,7 +10381,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
}
|
||||
}
|
||||
// Undefined for (int, int)
|
||||
ast::CmpOp::In | ast::CmpOp::NotIn => Err(CompareUnsupportedError {
|
||||
ast::CmpOp::In | ast::CmpOp::NotIn => Err(UnsupportedComparisonError {
|
||||
op,
|
||||
left_ty: left,
|
||||
right_ty: right,
|
||||
@@ -10405,7 +10394,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right,
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|_| UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
|
||||
}
|
||||
(Type::NominalInstance(_), Type::IntLiteral(_)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
@@ -10414,7 +10403,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
KnownClass::Int.to_instance(self.db()),
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|_|UnsupportedComparisonError { op, left_ty: left, right_ty: right }))
|
||||
}
|
||||
|
||||
// Booleans are coded as integers (False = 0, True = 1)
|
||||
@@ -10425,7 +10414,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Type::IntLiteral(i64::from(b)),
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|_|UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
|
||||
}
|
||||
(Type::BooleanLiteral(b), Type::IntLiteral(m)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
@@ -10434,7 +10423,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Type::IntLiteral(m),
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|_|UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
|
||||
}
|
||||
(Type::BooleanLiteral(a), Type::BooleanLiteral(b)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
@@ -10443,37 +10432,37 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
Type::IntLiteral(i64::from(b)),
|
||||
range,
|
||||
visitor,
|
||||
))
|
||||
).map_err(|_|UnsupportedComparisonError {op, left_ty: left, right_ty: right}))
|
||||
}
|
||||
|
||||
(Type::StringLiteral(salsa_s1), Type::StringLiteral(salsa_s2)) => {
|
||||
let s1 = salsa_s1.value(self.db());
|
||||
let s2 = salsa_s2.value(self.db());
|
||||
let result = match op {
|
||||
ast::CmpOp::Eq => Ok(Type::BooleanLiteral(s1 == s2)),
|
||||
ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(s1 != s2)),
|
||||
ast::CmpOp::Lt => Ok(Type::BooleanLiteral(s1 < s2)),
|
||||
ast::CmpOp::LtE => Ok(Type::BooleanLiteral(s1 <= s2)),
|
||||
ast::CmpOp::Gt => Ok(Type::BooleanLiteral(s1 > s2)),
|
||||
ast::CmpOp::GtE => Ok(Type::BooleanLiteral(s1 >= s2)),
|
||||
ast::CmpOp::In => Ok(Type::BooleanLiteral(s2.contains(s1))),
|
||||
ast::CmpOp::NotIn => Ok(Type::BooleanLiteral(!s2.contains(s1))),
|
||||
ast::CmpOp::Eq => Type::BooleanLiteral(s1 == s2),
|
||||
ast::CmpOp::NotEq => Type::BooleanLiteral(s1 != s2),
|
||||
ast::CmpOp::Lt => Type::BooleanLiteral(s1 < s2),
|
||||
ast::CmpOp::LtE => Type::BooleanLiteral(s1 <= s2),
|
||||
ast::CmpOp::Gt => Type::BooleanLiteral(s1 > s2),
|
||||
ast::CmpOp::GtE => Type::BooleanLiteral(s1 >= s2),
|
||||
ast::CmpOp::In => Type::BooleanLiteral(s2.contains(s1)),
|
||||
ast::CmpOp::NotIn => Type::BooleanLiteral(!s2.contains(s1)),
|
||||
ast::CmpOp::Is => {
|
||||
if s1 == s2 {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
KnownClass::Bool.to_instance(self.db())
|
||||
} else {
|
||||
Ok(Type::BooleanLiteral(false))
|
||||
Type::BooleanLiteral(false)
|
||||
}
|
||||
}
|
||||
ast::CmpOp::IsNot => {
|
||||
if s1 == s2 {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
KnownClass::Bool.to_instance(self.db())
|
||||
} else {
|
||||
Ok(Type::BooleanLiteral(true))
|
||||
Type::BooleanLiteral(true)
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(result)
|
||||
Some(Ok(result))
|
||||
}
|
||||
(Type::StringLiteral(_), _) => Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
@@ -10481,14 +10470,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right,
|
||||
range,
|
||||
visitor,
|
||||
)),
|
||||
).map_err(|err|UnsupportedComparisonError {op, left_ty: left, right_ty: err.right_ty})),
|
||||
(_, Type::StringLiteral(_)) => Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
range,
|
||||
visitor,
|
||||
)),
|
||||
).map_err(|err|UnsupportedComparisonError {op, left_ty: err.left_ty, right_ty: right})),
|
||||
|
||||
(Type::LiteralString, _) => Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
@@ -10496,47 +10485,47 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right,
|
||||
range,
|
||||
visitor,
|
||||
)),
|
||||
).map_err(|err|UnsupportedComparisonError {op, left_ty: left, right_ty: err.right_ty})),
|
||||
(_, Type::LiteralString) => Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
range,
|
||||
visitor,
|
||||
)),
|
||||
).map_err(|err|UnsupportedComparisonError {op, left_ty: err.left_ty, right_ty: right})),
|
||||
|
||||
(Type::BytesLiteral(salsa_b1), Type::BytesLiteral(salsa_b2)) => {
|
||||
let b1 = salsa_b1.value(self.db());
|
||||
let b2 = salsa_b2.value(self.db());
|
||||
let result = match op {
|
||||
ast::CmpOp::Eq => Ok(Type::BooleanLiteral(b1 == b2)),
|
||||
ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(b1 != b2)),
|
||||
ast::CmpOp::Lt => Ok(Type::BooleanLiteral(b1 < b2)),
|
||||
ast::CmpOp::LtE => Ok(Type::BooleanLiteral(b1 <= b2)),
|
||||
ast::CmpOp::Gt => Ok(Type::BooleanLiteral(b1 > b2)),
|
||||
ast::CmpOp::GtE => Ok(Type::BooleanLiteral(b1 >= b2)),
|
||||
ast::CmpOp::Eq => Type::BooleanLiteral(b1 == b2),
|
||||
ast::CmpOp::NotEq => Type::BooleanLiteral(b1 != b2),
|
||||
ast::CmpOp::Lt => Type::BooleanLiteral(b1 < b2),
|
||||
ast::CmpOp::LtE => Type::BooleanLiteral(b1 <= b2),
|
||||
ast::CmpOp::Gt => Type::BooleanLiteral(b1 > b2),
|
||||
ast::CmpOp::GtE => Type::BooleanLiteral(b1 >= b2),
|
||||
ast::CmpOp::In => {
|
||||
Ok(Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_some()))
|
||||
Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_some())
|
||||
}
|
||||
ast::CmpOp::NotIn => {
|
||||
Ok(Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_none()))
|
||||
Type::BooleanLiteral(memchr::memmem::find(b2, b1).is_none())
|
||||
}
|
||||
ast::CmpOp::Is => {
|
||||
if b1 == b2 {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
KnownClass::Bool.to_instance(self.db())
|
||||
} else {
|
||||
Ok(Type::BooleanLiteral(false))
|
||||
Type::BooleanLiteral(false)
|
||||
}
|
||||
}
|
||||
ast::CmpOp::IsNot => {
|
||||
if b1 == b2 {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
KnownClass::Bool.to_instance(self.db())
|
||||
} else {
|
||||
Ok(Type::BooleanLiteral(true))
|
||||
Type::BooleanLiteral(true)
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(result)
|
||||
Some(Ok(result))
|
||||
}
|
||||
(Type::BytesLiteral(_), _) => Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Bytes.to_instance(self.db()),
|
||||
@@ -10544,14 +10533,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right,
|
||||
range,
|
||||
visitor,
|
||||
)),
|
||||
).map_err(|err| UnsupportedComparisonError { op, left_ty: left, right_ty: err.right_ty })),
|
||||
(_, Type::BytesLiteral(_)) => Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Bytes.to_instance(self.db()),
|
||||
range,
|
||||
visitor,
|
||||
)),
|
||||
).map_err(|err| UnsupportedComparisonError { op, left_ty: err.left_ty, right_ty: right })),
|
||||
|
||||
(Type::EnumLiteral(literal_1), Type::EnumLiteral(literal_2))
|
||||
if op == ast::CmpOp::Eq =>
|
||||
@@ -10683,7 +10672,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right: Type<'db>,
|
||||
op: RichCompareOperator,
|
||||
policy: MemberLookupPolicy,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
) -> Result<Type<'db>, UnsupportedComparisonError<'db>> {
|
||||
let db = self.db();
|
||||
// The following resource has details about the rich comparison algorithm:
|
||||
// https://snarky.ca/unravelling-rich-comparison-operators/
|
||||
@@ -10719,7 +10708,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| CompareUnsupportedError {
|
||||
.ok_or_else(|| UnsupportedComparisonError {
|
||||
op: op.into(),
|
||||
left_ty: left,
|
||||
right_ty: right,
|
||||
@@ -10736,7 +10725,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right: Type<'db>,
|
||||
op: MembershipTestCompareOperator,
|
||||
range: TextRange,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
) -> Result<Type<'db>, UnsupportedComparisonError<'db>> {
|
||||
let db = self.db();
|
||||
|
||||
let contains_dunder = right.class_member(db, "__contains__".into()).place;
|
||||
@@ -10773,7 +10762,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
MembershipTestCompareOperator::NotIn => truthiness.negate().into_type(db),
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| CompareUnsupportedError {
|
||||
.ok_or_else(|| UnsupportedComparisonError {
|
||||
op: op.into(),
|
||||
left_ty: left,
|
||||
right_ty: right,
|
||||
@@ -10792,7 +10781,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
right: &TupleSpec<'db>,
|
||||
range: TextRange,
|
||||
visitor: &BinaryComparisonVisitor<'db>,
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
) -> Result<Type<'db>, UnsupportedComparisonError<'db>> {
|
||||
// If either tuple is variable length, we can make no assumptions about the relative
|
||||
// lengths of the tuples, and therefore neither about how they compare lexicographically.
|
||||
// TODO: Consider comparing the prefixes of the tuples, since that could give a comparison
|
||||
@@ -12382,11 +12371,22 @@ impl From<MembershipTestCompareOperator> for ast::CmpOp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Context for a failed comparison operation.
|
||||
///
|
||||
/// `left_ty` and `right_ty` are the "low-level" types
|
||||
/// that cannot be compared using `op`. For example,
|
||||
/// when evaluating `(1, "foo") < (2, 3)`, the "high-level"
|
||||
/// types of the operands are `tuple[Literal[1], Literal["foo"]]`
|
||||
/// and `tuple[Literal[2], Literal[3]]`. Those aren't captured
|
||||
/// in this struct, but the "low-level" types that mean that
|
||||
/// the high-level types cannot be compared *are* captured in
|
||||
/// this struct. In this case, those would be `Literal["foo"]`
|
||||
/// and `Literal[3]`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct CompareUnsupportedError<'db> {
|
||||
op: ast::CmpOp,
|
||||
left_ty: Type<'db>,
|
||||
right_ty: Type<'db>,
|
||||
pub(crate) struct UnsupportedComparisonError<'db> {
|
||||
pub(crate) op: ast::CmpOp,
|
||||
pub(crate) left_ty: Type<'db>,
|
||||
pub(crate) right_ty: Type<'db>,
|
||||
}
|
||||
|
||||
fn format_import_from_module(level: u32, module: Option<&str>) -> String {
|
||||
|
||||
Reference in New Issue
Block a user