Skip to content

Commit b0d6ec8

Browse files
committed
Document equality evaluation helpers
1 parent 1b3ddee commit b0d6ec8

1 file changed

Lines changed: 69 additions & 7 deletions

File tree

crates/ty_python_semantic/src/types/equality.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ use super::{
88
enums::{enum_member_literals, enum_metadata},
99
};
1010

11+
/// The result of evaluating a runtime comparison between two types.
12+
///
13+
/// Definite truthiness is represented separately from a constraint for the operand currently being
14+
/// narrowed. A comparison can therefore be ambiguous at runtime while still constraining that
15+
/// operand in either branch.
1116
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1217
enum ComparisonResult<'db> {
1318
/// The comparison always evaluates to true.
@@ -18,7 +23,7 @@ enum ComparisonResult<'db> {
1823
/// object of type `Literal[Foo.X]` in the following example, despite the fact that
1924
/// `Literal[1]` is disjoint from `Literal[Foo.X]`:
2025
///
21-
/// ```py
26+
/// ```python
2227
/// from enum import IntEnum
2328
///
2429
/// class Foo(IntEnum):
@@ -43,6 +48,7 @@ enum ComparisonResult<'db> {
4348
}
4449

4550
impl<'db> ComparisonResult<'db> {
51+
/// Convert this result into a constraint for a branch with the given truthiness.
4652
fn constraint(self, is_positive: bool) -> Option<Type<'db>> {
4753
match self {
4854
ComparisonResult::AlwaysTrue => (!is_positive).then_some(Type::Never),
@@ -52,6 +58,7 @@ impl<'db> ComparisonResult<'db> {
5258
}
5359
}
5460

61+
/// Preserve definite truthiness while discarding a conditional narrowing result.
5562
fn discard_narrowing(self) -> Self {
5663
match self {
5764
ComparisonResult::CanNarrow(_) => ComparisonResult::Ambiguous,
@@ -60,6 +67,10 @@ impl<'db> ComparisonResult<'db> {
6067
}
6168
}
6269

70+
/// Return a constraint for `left` in a branch where `left == right` has the given truthiness.
71+
///
72+
/// Returns `None` when the comparison behavior of either operand is not precise enough to safely
73+
/// constrain `left`.
6374
pub(super) fn evaluate_type_equality<'db>(
6475
db: &'db dyn Db,
6576
left: Type<'db>,
@@ -79,6 +90,10 @@ pub(super) fn evaluate_type_equality<'db>(
7990
})
8091
}
8192

93+
/// Return a constraint for `left` in a branch where `left != right` has the given truthiness.
94+
///
95+
/// Returns `None` when the comparison behavior of either operand is not precise enough to safely
96+
/// constrain `left`.
8297
pub(super) fn evaluate_type_inequality<'db>(
8398
db: &'db dyn Db,
8499
left: Type<'db>,
@@ -96,6 +111,9 @@ pub(super) fn evaluate_type_inequality<'db>(
96111
.or_else(|| inequality_result(db, left, right, is_positive).constraint(is_positive))
97112
}
98113

114+
/// Return the truthiness of `left == right` when it is known for every represented runtime value.
115+
///
116+
/// A result that only permits narrowing remains ambiguous because it can still evaluate either way.
99117
pub(crate) fn equality_truthiness<'db>(
100118
db: &'db dyn Db,
101119
left: Type<'db>,
@@ -108,6 +126,10 @@ pub(crate) fn equality_truthiness<'db>(
108126
}
109127
}
110128

129+
/// Evaluate equality recursively, treating `left` as the operand being constrained.
130+
///
131+
/// `is_positive` selects the branch whose constraint is accumulated when either operand expands
132+
/// into multiple alternatives.
111133
fn equality_result<'db>(
112134
db: &'db dyn Db,
113135
left: Type<'db>,
@@ -280,12 +302,6 @@ fn equality_result<'db>(
280302
}
281303
}
282304

283-
/// Return a constraint that does not depend on the target's currently inferred literal union.
284-
///
285-
/// Narrowing constraints participate in cyclic inference. Filtering `"B" | "C"` to `"B"` for the
286-
/// false branch of `x == "C"` can freeze a loop before later iterations widen `x`. Constraining the
287-
/// target with `~Literal["C"]` instead describes the predicate itself and remains valid as the cycle
288-
/// reaches its fixed point.
289305
fn is_builtin_primitive(db: &dyn Db, ty: Type) -> bool {
290306
match ty.resolve_type_alias(db) {
291307
Type::LiteralValue(literal) => matches!(
@@ -300,6 +316,15 @@ fn is_builtin_primitive(db: &dyn Db, ty: Type) -> bool {
300316
}
301317
}
302318

319+
/// Return a predicate-shaped constraint for comparison with a primitive literal.
320+
///
321+
/// Narrowing constraints participate in cyclic inference. Filtering `"B" | "C"` to `"B"` for the
322+
/// false branch of `x == "C"` can freeze a loop before later iterations widen `x`. Constraining the
323+
/// target with `~Literal["C"]` instead describes the predicate itself and remains valid as the cycle
324+
/// reaches its fixed point.
325+
///
326+
/// The constraint also follows Python's equality between booleans and integers: `x != 0` excludes
327+
/// both `Literal[0]` and `Literal[False]`, while `x != 1` excludes `Literal[1]` and `Literal[True]`.
303328
fn primitive_literal_constraint<'db>(
304329
db: &'db dyn Db,
305330
left: Type<'db>,
@@ -381,6 +406,10 @@ fn primitive_literal_constraint<'db>(
381406
.then_some(equal_to_right)
382407
}
383408

409+
/// Return a direct enum-member constraint when the target is entirely from the same enum domain.
410+
///
411+
/// This is only valid when the enum inherits comparison behavior that `ty` understands; custom
412+
/// equality or inequality methods make the result ambiguous.
384413
fn enum_literal_constraint<'db>(
385414
db: &'db dyn Db,
386415
left: Type<'db>,
@@ -425,6 +454,10 @@ fn is_same_enum_domain<'db>(db: &'db dyn Db, ty: Type<'db>, right: EnumLiteralTy
425454
}
426455
}
427456

457+
/// Evaluate inequality recursively, treating `left` as the operand being constrained.
458+
///
459+
/// `is_positive` selects the branch whose constraint is accumulated when either operand expands
460+
/// into multiple alternatives.
428461
fn inequality_result<'db>(
429462
db: &'db dyn Db,
430463
left: Type<'db>,
@@ -640,6 +673,10 @@ fn evaluate_union_left<'db>(
640673
})
641674
}
642675

676+
/// Combine comparison results for the alternatives of the union being constrained.
677+
///
678+
/// Alternatives that cannot satisfy the selected branch are removed. Dynamic alternatives retain
679+
/// negative constraints for removed arms so that the result still describes the branch predicate.
643680
fn evaluate_target_union<'db>(
644681
db: &'db dyn Db,
645682
elements: &[Type<'db>],
@@ -734,6 +771,10 @@ fn evaluate_union_right<'db>(
734771
)
735772
}
736773

774+
/// Combine comparison results produced by alternatives of the non-target operand.
775+
///
776+
/// The target remains possible when any alternative can satisfy the selected branch; definite
777+
/// truthiness is reported only when every alternative agrees.
737778
fn evaluate_against_results<'db>(
738779
db: &'db dyn Db,
739780
target: Type<'db>,
@@ -830,6 +871,10 @@ fn inequality_alternatives<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Ty
830871
finite_alternatives(db, ty, ComparisonOperator::Inequality)
831872
}
832873

874+
/// Expand a type into its finite runtime alternatives when its comparison semantics are known.
875+
///
876+
/// Enum classes with custom comparison methods are deliberately not expanded because their members
877+
/// may compare equal to values outside the enum domain.
833878
fn finite_alternatives<'db>(
834879
db: &'db dyn Db,
835880
ty: Type<'db>,
@@ -887,6 +932,7 @@ fn narrow_literal_comparison<'db>(
887932
}
888933
}
889934

935+
/// Narrow `LiteralString` against a string-valued enum member with inherited `str` semantics.
890936
fn narrow_literal_string_against_enum<'db>(
891937
db: &'db dyn Db,
892938
enum_literal: EnumLiteralType<'db>,
@@ -1001,6 +1047,10 @@ impl ComparisonOperator {
10011047
}
10021048
}
10031049

1050+
/// A known builtin implementation that determines the runtime behavior of a comparison.
1051+
///
1052+
/// Two types with different known semantics cannot compare equal. Types with custom or otherwise
1053+
/// unknown comparison methods are not assigned a value of this enum.
10041054
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10051055
enum KnownComparisonSemantics {
10061056
Object,
@@ -1067,6 +1117,9 @@ fn comparison_domain<'db>(
10671117
}
10681118
}
10691119

1120+
/// Determine the builtin comparison implementation inherited by `ty`.
1121+
///
1122+
/// Returns `None` when dunder lookup finds custom or conflicting comparison behavior.
10701123
fn comparison_semantics<'db>(
10711124
db: &'db dyn Db,
10721125
ty: Type<'db>,
@@ -1108,6 +1161,7 @@ fn comparison_semantics<'db>(
11081161
}
11091162
}
11101163

1164+
/// Return whether `ty` is a singleton whose comparison uses object identity semantics.
11111165
fn has_known_identity_comparison_semantics<'db>(
11121166
db: &'db dyn Db,
11131167
ty: Type<'db>,
@@ -1243,6 +1297,10 @@ fn lookup_dunder<'db>(
12431297
)
12441298
}
12451299

1300+
/// Return the comparison result for two literals when their runtime values determine it.
1301+
///
1302+
/// This accounts for integer/boolean equality and enum aliases or enum values. `None` means custom
1303+
/// or insufficiently known comparison behavior prevents a definitive result.
12461304
fn known_literal_equality<'db>(
12471305
db: &'db dyn Db,
12481306
left: LiteralValueTypeKind<'db>,
@@ -1323,6 +1381,9 @@ fn known_type_value_equality<'db>(
13231381
)
13241382
}
13251383

1384+
/// Return the statically known runtime value of an enum member.
1385+
///
1386+
/// Custom enum construction can replace the declared value, so members of such enums return `None`.
13261387
fn enum_literal_value<'db>(db: &'db dyn Db, literal: EnumLiteralType<'db>) -> Option<Type<'db>> {
13271388
let metadata = enum_metadata(db, literal.enum_class(db))?;
13281389
let name = metadata.resolve_member(literal.name(db))?;
@@ -1339,6 +1400,7 @@ fn enum_literal_value<'db>(db: &'db dyn Db, literal: EnumLiteralType<'db>) -> Op
13391400
}
13401401
}
13411402

1403+
/// Return whether two enum literals resolve to the same member, including aliases.
13421404
fn same_enum_member<'db>(
13431405
db: &'db dyn Db,
13441406
left: EnumLiteralType<'db>,

0 commit comments

Comments
 (0)