Skip to content

Commit 7b35edf

Browse files
committed
Iterators are reversed (not working yet)
1 parent b4e4996 commit 7b35edf

File tree

5 files changed

+197
-115
lines changed

5 files changed

+197
-115
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
source: crates/red_knot_test/src/lib.rs
3+
assertion_line: 272
4+
expression: snapshot
5+
---
6+
---
7+
mdtest name: for.md - For loops - With non-callable iterator
8+
mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
9+
---
10+
11+
# Python source files
12+
13+
## mdtest_snippet.py
14+
15+
```
16+
1 | from typing_extensions import reveal_type
17+
2 |
18+
3 | def _(flag: bool):
19+
4 | class NotIterable:
20+
5 | if flag:
21+
6 | __iter__: int = 1
22+
7 | else:
23+
8 | __iter__: None = None
24+
9 |
25+
10 | # error: [not-iterable]
26+
11 | for x in NotIterable():
27+
12 | pass
28+
13 |
29+
14 | # revealed: Unknown
30+
15 | # error: [possibly-unresolved-reference]
31+
16 | reveal_type(x)
32+
```
33+
34+
# Diagnostics
35+
36+
```
37+
error: lint:not-iterable
38+
--> /src/mdtest_snippet.py:11:14
39+
|
40+
10 | # error: [not-iterable]
41+
11 | for x in NotIterable():
42+
| ^^^^^^^^^^^^^ Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `int | None`, which is not callable
43+
12 | pass
44+
|
45+
46+
```
47+
48+
```
49+
info: revealed-type
50+
--> /src/mdtest_snippet.py:16:5
51+
|
52+
14 | # revealed: Unknown
53+
15 | # error: [possibly-unresolved-reference]
54+
16 | reveal_type(x)
55+
| -------------- info: Revealed type is `Unknown`
56+
|
57+
58+
```

crates/red_knot_python_semantic/src/semantic_index.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,12 +426,14 @@ mod tests {
426426
impl UseDefMap<'_> {
427427
fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
428428
self.public_bindings(symbol)
429-
.find_map(|constrained_binding| constrained_binding.binding)
429+
.filter_map(|constrained_binding| constrained_binding.binding)
430+
.last()
430431
}
431432

432433
fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
433434
self.bindings_at_use(use_id)
434-
.find_map(|constrained_binding| constrained_binding.binding)
435+
.filter_map(|constrained_binding| constrained_binding.binding)
436+
.last()
435437
}
436438
}
437439

crates/red_knot_python_semantic/src/semantic_index/use_def/symbol_state.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ mod tests {
485485
symbol: &SymbolState,
486486
expected: &[&str],
487487
) {
488-
let actual = symbol_states
488+
let mut actual = symbol_states
489489
.bindings
490490
.iter_reverse(symbol.bindings.live_bindings)
491491
.map(|(def_id, live_binding)| {
@@ -502,6 +502,7 @@ mod tests {
502502
format!("{def}<{constraints}>")
503503
})
504504
.collect::<Vec<_>>();
505+
actual.reverse();
505506
assert_eq!(actual, expected);
506507
}
507508

@@ -511,7 +512,7 @@ mod tests {
511512
symbol: &SymbolState,
512513
expected: &[&str],
513514
) {
514-
let actual = symbol_states
515+
let mut actual = symbol_states
515516
.declarations
516517
.iter_reverse(symbol.declarations.live_declarations)
517518
.map(|(declaration, _)| {
@@ -522,6 +523,7 @@ mod tests {
522523
}
523524
})
524525
.collect::<Vec<_>>();
526+
actual.reverse();
525527
assert_eq!(actual, expected);
526528
}
527529

crates/red_knot_python_semantic/src/symbol.rs

Lines changed: 129 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -557,66 +557,78 @@ fn symbol_from_bindings_impl<'db>(
557557
_ => Truthiness::AlwaysFalse,
558558
};
559559

560-
let mut types = bindings_with_constraints.filter_map(
561-
|BindingWithConstraints {
562-
binding,
563-
narrowing_constraint,
564-
visibility_constraint,
565-
}| {
566-
let binding = binding?;
567-
568-
if is_non_exported(binding) {
569-
return None;
570-
}
571-
572-
let static_visibility =
573-
visibility_constraints.evaluate(db, constraints, visibility_constraint);
574-
575-
if static_visibility.is_always_false() {
576-
return None;
577-
}
560+
let mut types: Vec<_> = bindings_with_constraints
561+
.filter_map(
562+
|BindingWithConstraints {
563+
binding,
564+
narrowing_constraint,
565+
visibility_constraint,
566+
}| {
567+
let binding = binding?;
568+
569+
if is_non_exported(binding) {
570+
return None;
571+
}
578572

579-
let constraint_tys: Vec<_> = narrowing_constraint
580-
.filter_map(|constraint| infer_narrowing_constraint(db, constraint, binding))
581-
.collect();
573+
let static_visibility =
574+
visibility_constraints.evaluate(db, constraints, visibility_constraint);
582575

583-
let binding_ty = binding_type(db, binding);
584-
if constraint_tys.is_empty() {
585-
Some(binding_ty)
586-
} else {
587-
let intersection_ty = constraint_tys
588-
.into_iter()
589-
.rev()
590-
.fold(
591-
IntersectionBuilder::new(db).add_positive(binding_ty),
592-
IntersectionBuilder::add_positive,
593-
)
594-
.build();
595-
Some(intersection_ty)
596-
}
597-
},
598-
);
576+
if static_visibility.is_always_false() {
577+
return None;
578+
}
599579

600-
if let Some(first) = types.next() {
601-
let boundness = match unbound_visibility {
602-
Truthiness::AlwaysTrue => {
603-
unreachable!("If we have at least one binding, the scope-start should not be definitely visible")
604-
}
605-
Truthiness::AlwaysFalse => Boundness::Bound,
606-
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
607-
};
580+
let constraint_tys: Vec<_> = narrowing_constraint
581+
.filter_map(|constraint| infer_narrowing_constraint(db, constraint, binding))
582+
.collect();
583+
584+
let binding_ty = binding_type(db, binding);
585+
if constraint_tys.is_empty() {
586+
Some(binding_ty)
587+
} else {
588+
let intersection_ty = constraint_tys
589+
.into_iter()
590+
.rev()
591+
.fold(
592+
IntersectionBuilder::new(db).add_positive(binding_ty),
593+
IntersectionBuilder::add_positive,
594+
)
595+
.build();
596+
Some(intersection_ty)
597+
}
598+
},
599+
)
600+
.collect();
601+
602+
// The bindings iterator is in reverse order, so popping the last element from the collected
603+
// vector gives us the first binding.
604+
let Some(first) = types.pop() else {
605+
return Symbol::Unbound;
606+
};
608607

609-
if let Some(second) = types.next() {
610-
Symbol::Type(
611-
UnionType::from_elements(db, [first, second].into_iter().chain(types)),
612-
boundness,
608+
let boundness = match unbound_visibility {
609+
Truthiness::AlwaysTrue => {
610+
unreachable!(
611+
"If we have at least one binding, the scope-start should not be definitely visible"
613612
)
614-
} else {
615-
Symbol::Type(first, boundness)
616613
}
617-
} else {
618-
Symbol::Unbound
614+
Truthiness::AlwaysFalse => Boundness::Bound,
615+
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
616+
};
617+
618+
if types.is_empty() {
619+
return Symbol::Type(first, boundness);
619620
}
621+
622+
Symbol::Type(
623+
types
624+
.into_iter()
625+
.rev()
626+
.fold(UnionBuilder::new(db).add(first), |builder, element| {
627+
builder.add(element)
628+
})
629+
.build(),
630+
boundness,
631+
)
620632
}
621633

622634
/// Implementation of [`symbol_from_declarations`].
@@ -647,70 +659,77 @@ fn symbol_from_declarations_impl<'db>(
647659
_ => Truthiness::AlwaysFalse,
648660
};
649661

650-
let mut types = declarations.filter_map(
651-
|DeclarationWithConstraint {
652-
declaration,
653-
visibility_constraint,
654-
}| {
655-
let declaration = declaration?;
662+
let mut types: Vec<_> = declarations
663+
.filter_map(
664+
|DeclarationWithConstraint {
665+
declaration,
666+
visibility_constraint,
667+
}| {
668+
let declaration = declaration?;
656669

657-
if is_non_exported(declaration) {
658-
return None;
659-
}
670+
if is_non_exported(declaration) {
671+
return None;
672+
}
660673

661-
let static_visibility =
662-
visibility_constraints.evaluate(db, constraints, visibility_constraint);
674+
let static_visibility =
675+
visibility_constraints.evaluate(db, constraints, visibility_constraint);
663676

664-
if static_visibility.is_always_false() {
665-
None
666-
} else {
667-
Some(declaration_type(db, declaration))
668-
}
669-
},
670-
);
671-
672-
if let Some(first) = types.next() {
673-
let mut conflicting: Vec<Type<'db>> = vec![];
674-
let declared_ty = if let Some(second) = types.next() {
675-
let ty_first = first.inner_type();
676-
let mut qualifiers = first.qualifiers();
677-
678-
let mut builder = UnionBuilder::new(db).add(ty_first);
679-
for other in std::iter::once(second).chain(types) {
680-
let other_ty = other.inner_type();
681-
if !ty_first.is_equivalent_to(db, other_ty) {
682-
conflicting.push(other_ty);
677+
if static_visibility.is_always_false() {
678+
None
679+
} else {
680+
Some(declaration_type(db, declaration))
683681
}
684-
builder = builder.add(other_ty);
685-
qualifiers = qualifiers.union(other.qualifiers());
686-
}
687-
TypeAndQualifiers::new(builder.build(), qualifiers)
688-
} else {
689-
first
690-
};
691-
if conflicting.is_empty() {
692-
let boundness = match undeclared_visibility {
693-
Truthiness::AlwaysTrue => {
694-
unreachable!("If we have at least one declaration, the scope-start should not be definitely visible")
695-
}
696-
Truthiness::AlwaysFalse => Boundness::Bound,
697-
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
698-
};
682+
},
683+
)
684+
.collect();
685+
686+
// The bindings iterator is in reverse order, so popping the last element from the collected
687+
// vector gives us the first binding.
688+
let Some(first) = types.pop() else {
689+
return Ok(Symbol::Unbound.into());
690+
};
699691

700-
Ok(SymbolAndQualifiers(
701-
Symbol::Type(declared_ty.inner_type(), boundness),
702-
declared_ty.qualifiers(),
703-
))
704-
} else {
705-
Err((
706-
declared_ty,
707-
std::iter::once(first.inner_type())
708-
.chain(conflicting)
709-
.collect(),
710-
))
692+
let boundness = match undeclared_visibility {
693+
Truthiness::AlwaysTrue => {
694+
unreachable!("If we have at least one declaration, the scope-start should not be definitely visible")
695+
}
696+
Truthiness::AlwaysFalse => Boundness::Bound,
697+
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
698+
};
699+
700+
if types.is_empty() {
701+
return Ok(SymbolAndQualifiers(
702+
Symbol::Type(first.inner_type(), boundness),
703+
first.qualifiers(),
704+
));
705+
}
706+
707+
let mut conflicting: Vec<Type<'db>> = vec![];
708+
let ty_first = first.inner_type();
709+
let mut qualifiers = first.qualifiers();
710+
711+
let mut builder = UnionBuilder::new(db).add(ty_first);
712+
for other in types.into_iter().rev() {
713+
let other_ty = other.inner_type();
714+
if !ty_first.is_equivalent_to(db, other_ty) {
715+
conflicting.push(other_ty);
711716
}
717+
builder = builder.add(other_ty);
718+
qualifiers = qualifiers.union(other.qualifiers());
719+
}
720+
let declared_ty = builder.build();
721+
722+
if conflicting.is_empty() {
723+
Ok(SymbolAndQualifiers(
724+
Symbol::Type(declared_ty, boundness),
725+
qualifiers,
726+
))
712727
} else {
713-
Ok(Symbol::Unbound.into())
728+
conflicting.insert(0, ty_first);
729+
Err((
730+
TypeAndQualifiers::new(declared_ty, qualifiers),
731+
conflicting.into(),
732+
))
714733
}
715734
}
716735

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6698,7 +6698,8 @@ mod tests {
66986698
let scope = global_scope(db, file);
66996699
use_def_map(db, scope)
67006700
.public_bindings(symbol_table(db, scope).symbol_id_by_name(name).unwrap())
6701-
.find_map(|b| b.binding)
6701+
.filter_map(|b| b.binding)
6702+
.last()
67026703
.expect("no binding found")
67036704
}
67046705

0 commit comments

Comments
 (0)