Skip to content

Commit 5df70ef

Browse files
committed
[ty] simplify return type of place_from_declarations
1 parent 79c949f commit 5df70ef

File tree

10 files changed

+239
-198
lines changed

10 files changed

+239
-198
lines changed

crates/ty_python_semantic/resources/mdtest/annotations/invalid.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def _(
5656
def bar() -> None:
5757
return None
5858

59+
async def baz(): ...
5960
async def outer(): # avoid unrelated syntax errors on yield, yield from, and await
6061
def _(
6162
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
@@ -69,7 +70,7 @@ async def outer(): # avoid unrelated syntax errors on yield, yield from, and aw
6970
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
7071
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
7172
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
72-
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
73+
l: await baz(), # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
7374
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
7475
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
7576
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"

crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ match obj:
124124
## `return`, `yield`, `yield from`, and `await` outside function
125125

126126
```py
127+
class C:
128+
def __await__(self): ...
129+
127130
# error: [invalid-syntax] "`return` statement outside of a function"
128131
return
129132

@@ -135,11 +138,11 @@ yield from []
135138

136139
# error: [invalid-syntax] "`await` statement outside of a function"
137140
# error: [invalid-syntax] "`await` outside of an asynchronous function"
138-
await 1
141+
await C()
139142

140143
def f():
141144
# error: [invalid-syntax] "`await` outside of an asynchronous function"
142-
await 1
145+
await C()
143146
```
144147

145148
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
@@ -330,7 +333,8 @@ async def elements(n):
330333

331334
def _():
332335
# error: [invalid-syntax] "`await` outside of an asynchronous function"
333-
await 1
336+
await elements(1)
337+
334338
# error: [invalid-syntax] "`async for` outside of an asynchronous function"
335339
async for _ in elements(1):
336340
...

crates/ty_python_semantic/resources/mdtest/scopes/unbound.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ class C:
4040
reveal_type(C.y) # revealed: Unknown | Literal[1, "abc"]
4141
```
4242

43+
## Possibly unbound in class scope with multiple declarations
44+
45+
```py
46+
def coinflip() -> bool:
47+
return True
48+
49+
class C:
50+
if coinflip():
51+
x: int = 1
52+
elif coinflip():
53+
x: str = "abc"
54+
55+
# error: [possibly-unbound-attribute]
56+
reveal_type(C.x) # revealed: int | str
57+
```
58+
4359
## Unbound function local
4460

4561
An unbound function local that has definitions in the scope does not fall back to globals.

crates/ty_python_semantic/src/place.rs

Lines changed: 95 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -472,12 +472,56 @@ pub(crate) fn place_from_declarations<'db>(
472472
place_from_declarations_impl(db, declarations, RequiresExplicitReExport::No)
473473
}
474474

475-
pub(crate) type DeclaredTypeAndConflictingTypes<'db> =
476-
(TypeAndQualifiers<'db>, Box<indexmap::set::Slice<Type<'db>>>);
475+
type DeclaredTypeAndConflictingTypes<'db> = (
476+
TypeAndQualifiers<'db>,
477+
Option<Box<indexmap::set::Slice<Type<'db>>>>,
478+
);
477479

478480
/// The result of looking up a declared type from declarations; see [`place_from_declarations`].
479-
pub(crate) type PlaceFromDeclarationsResult<'db> =
480-
Result<PlaceAndQualifiers<'db>, DeclaredTypeAndConflictingTypes<'db>>;
481+
pub(crate) struct PlaceFromDeclarationsResult<'db> {
482+
place_and_quals: PlaceAndQualifiers<'db>,
483+
conflicting_types: Option<Box<indexmap::set::Slice<Type<'db>>>>,
484+
}
485+
486+
impl<'db> PlaceFromDeclarationsResult<'db> {
487+
fn conflict(
488+
place_and_quals: PlaceAndQualifiers<'db>,
489+
conflicting_types: Box<indexmap::set::Slice<Type<'db>>>,
490+
) -> Self {
491+
PlaceFromDeclarationsResult {
492+
place_and_quals,
493+
conflicting_types: Some(conflicting_types),
494+
}
495+
}
496+
497+
pub(crate) fn ignore_conflicting_declarations(self) -> PlaceAndQualifiers<'db> {
498+
self.place_and_quals
499+
}
500+
501+
pub(crate) fn with_conflicting_declarations(
502+
self,
503+
) -> (
504+
PlaceAndQualifiers<'db>,
505+
Option<Box<indexmap::set::Slice<Type<'db>>>>,
506+
) {
507+
(self.place_and_quals, self.conflicting_types)
508+
}
509+
}
510+
511+
impl<'db> From<PlaceAndQualifiers<'db>> for PlaceFromDeclarationsResult<'db> {
512+
fn from(place_and_quals: PlaceAndQualifiers<'db>) -> Self {
513+
PlaceFromDeclarationsResult {
514+
place_and_quals,
515+
conflicting_types: None,
516+
}
517+
}
518+
}
519+
520+
impl<'db> From<Place<'db>> for PlaceFromDeclarationsResult<'db> {
521+
fn from(place: Place<'db>) -> Self {
522+
PlaceFromDeclarationsResult::from(PlaceAndQualifiers::from(place))
523+
}
524+
}
481525

482526
/// A type with declaredness information, and a set of type qualifiers.
483527
///
@@ -659,7 +703,8 @@ fn place_by_id<'db>(
659703
ConsideredDefinitions::AllReachable => use_def.all_reachable_declarations(place_id),
660704
};
661705

662-
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport);
706+
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport)
707+
.ignore_conflicting_declarations();
663708

664709
let all_considered_bindings = || match considered_definitions {
665710
ConsideredDefinitions::EndOfScope => use_def.end_of_scope_bindings(place_id),
@@ -668,53 +713,41 @@ fn place_by_id<'db>(
668713

669714
// If a symbol is undeclared, but qualified with `typing.Final`, we use the right-hand side
670715
// inferred type, without unioning with `Unknown`, because it can not be modified.
671-
if let Some(qualifiers) = declared
672-
.as_ref()
673-
.ok()
674-
.and_then(PlaceAndQualifiers::is_bare_final)
675-
{
716+
if let Some(qualifiers) = declared.is_bare_final() {
676717
let bindings = all_considered_bindings();
677718
return place_from_bindings_impl(db, bindings, requires_explicit_reexport)
678719
.with_qualifiers(qualifiers);
679720
}
680721

681-
// Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the
682-
// inferred type.
683722
match declared {
684-
Ok(PlaceAndQualifiers {
723+
// Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the
724+
// inferred type.
725+
PlaceAndQualifiers {
685726
place: Place::Type(Type::Dynamic(DynamicType::Unknown), declaredness),
686727
qualifiers,
687-
}) if qualifiers.contains(TypeQualifiers::CLASS_VAR) => {
728+
} if qualifiers.contains(TypeQualifiers::CLASS_VAR) => {
688729
let bindings = all_considered_bindings();
689730
match place_from_bindings_impl(db, bindings, requires_explicit_reexport) {
690-
Place::Type(inferred, boundness) => {
691-
return Place::Type(
692-
UnionType::from_elements(db, [Type::unknown(), inferred]),
693-
boundness,
694-
)
695-
.with_qualifiers(qualifiers);
696-
}
731+
Place::Type(inferred, boundness) => Place::Type(
732+
UnionType::from_elements(db, [Type::unknown(), inferred]),
733+
boundness,
734+
)
735+
.with_qualifiers(qualifiers),
697736
Place::Unbound => {
698-
return Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers);
737+
Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers)
699738
}
700739
}
701740
}
702-
_ => {}
703-
}
704-
705-
match declared {
706741
// Place is declared, trust the declared type
707-
Ok(
708-
place_and_quals @ PlaceAndQualifiers {
709-
place: Place::Type(_, Boundness::Bound),
710-
qualifiers: _,
711-
},
712-
) => place_and_quals,
742+
place_and_quals @ PlaceAndQualifiers {
743+
place: Place::Type(_, Boundness::Bound),
744+
qualifiers: _,
745+
} => place_and_quals,
713746
// Place is possibly declared
714-
Ok(PlaceAndQualifiers {
747+
PlaceAndQualifiers {
715748
place: Place::Type(declared_ty, Boundness::PossiblyUnbound),
716749
qualifiers,
717-
}) => {
750+
} => {
718751
let bindings = all_considered_bindings();
719752
let boundness_analysis = bindings.boundness_analysis;
720753
let inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
@@ -741,10 +774,10 @@ fn place_by_id<'db>(
741774
PlaceAndQualifiers { place, qualifiers }
742775
}
743776
// Place is undeclared, return the union of `Unknown` with the inferred type
744-
Ok(PlaceAndQualifiers {
777+
PlaceAndQualifiers {
745778
place: Place::Unbound,
746779
qualifiers: _,
747-
}) => {
780+
} => {
748781
let bindings = all_considered_bindings();
749782
let boundness_analysis = bindings.boundness_analysis;
750783
let mut inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
@@ -786,12 +819,6 @@ fn place_by_id<'db>(
786819
.into()
787820
}
788821
}
789-
// Place has conflicting declared types
790-
Err((declared, _)) => {
791-
// Intentionally ignore conflicting declared types; that's not our problem,
792-
// it's the problem of the module we are importing from.
793-
Place::bound(declared.inner_type()).with_qualifiers(declared.qualifiers())
794-
}
795822
}
796823

797824
// TODO (ticket: https://github.com/astral-sh/ruff/issues/14297) Our handling of boundness
@@ -1129,18 +1156,20 @@ impl<'db> DeclaredTypeBuilder<'db> {
11291156
}
11301157

11311158
fn build(mut self) -> DeclaredTypeAndConflictingTypes<'db> {
1132-
if !self.conflicting_types.is_empty() {
1159+
let type_and_quals = TypeAndQualifiers::new(self.inner.build(), self.qualifiers);
1160+
if self.conflicting_types.is_empty() {
1161+
(type_and_quals, None)
1162+
} else {
11331163
self.conflicting_types.insert_before(
11341164
0,
11351165
self.first_type
11361166
.expect("there must be a first type if there are conflicting types"),
11371167
);
1168+
(
1169+
type_and_quals,
1170+
Some(self.conflicting_types.into_boxed_slice()),
1171+
)
11381172
}
1139-
1140-
(
1141-
TypeAndQualifiers::new(self.inner.build(), self.qualifiers),
1142-
self.conflicting_types.into_boxed_slice(),
1143-
)
11441173
}
11451174
}
11461175

@@ -1203,22 +1232,16 @@ fn place_from_declarations_impl<'db>(
12031232
);
12041233

12051234
if let Some(first) = types.next() {
1206-
let declared = if let Some(second) = types.next() {
1235+
let (declared, conflicting) = if let Some(second) = types.next() {
12071236
let mut builder = DeclaredTypeBuilder::new(db);
12081237
builder.add(first);
12091238
builder.add(second);
12101239
for element in types {
12111240
builder.add(element);
12121241
}
1213-
let (union, conflicting) = builder.build();
1214-
1215-
if !conflicting.is_empty() {
1216-
return Err((union, conflicting));
1217-
}
1218-
1219-
union
1242+
builder.build()
12201243
} else {
1221-
first
1244+
(first, None)
12221245
};
12231246

12241247
let boundness = match boundness_analysis {
@@ -1244,9 +1267,16 @@ fn place_from_declarations_impl<'db>(
12441267
},
12451268
};
12461269

1247-
Ok(Place::Type(declared.inner_type(), boundness).with_qualifiers(declared.qualifiers()))
1270+
let place_and_quals =
1271+
Place::Type(declared.inner_type(), boundness).with_qualifiers(declared.qualifiers());
1272+
1273+
if let Some(conflicting) = conflicting {
1274+
PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting)
1275+
} else {
1276+
place_and_quals.into()
1277+
}
12481278
} else {
1249-
Ok(Place::Unbound.into())
1279+
Place::Unbound.into()
12501280
}
12511281
}
12521282

@@ -1281,31 +1311,32 @@ mod implicit_globals {
12811311
use crate::semantic_index::{place_table, use_def_map};
12821312
use crate::types::{KnownClass, Type};
12831313

1284-
use super::{Place, PlaceFromDeclarationsResult, place_from_declarations};
1314+
use super::{Place, place_from_declarations};
12851315

12861316
pub(crate) fn module_type_implicit_global_declaration<'db>(
12871317
db: &'db dyn Db,
12881318
name: &str,
1289-
) -> PlaceFromDeclarationsResult<'db> {
1319+
) -> PlaceAndQualifiers<'db> {
12901320
if !module_type_symbols(db)
12911321
.iter()
12921322
.any(|module_type_member| module_type_member == name)
12931323
{
1294-
return Ok(Place::Unbound.into());
1324+
return Place::Unbound.into();
12951325
}
12961326
let Type::ClassLiteral(module_type_class) = KnownClass::ModuleType.to_class_literal(db)
12971327
else {
1298-
return Ok(Place::Unbound.into());
1328+
return Place::Unbound.into();
12991329
};
13001330
let module_type_scope = module_type_class.body_scope(db);
13011331
let place_table = place_table(db, module_type_scope);
13021332
let Some(symbol_id) = place_table.symbol_id(name) else {
1303-
return Ok(Place::Unbound.into());
1333+
return Place::Unbound.into();
13041334
};
13051335
place_from_declarations(
13061336
db,
13071337
use_def_map(db, module_type_scope).end_of_scope_symbol_declarations(symbol_id),
13081338
)
1339+
.ignore_conflicting_declarations()
13091340
}
13101341

13111342
/// Looks up the type of an "implicit global symbol". Returns [`Place::Unbound`] if

0 commit comments

Comments
 (0)