mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:14:52 +00:00
[ty] simplify return type of place_from_declarations (#19884)
## Summary A [passing comment](https://github.com/astral-sh/ruff/pull/19711#issuecomment-3169312014) led me to explore why we didn't report a class attribute as possibly unbound if it was a method and defined in two different conditional branches. I found that the reason was because of our handling of "conflicting declarations" in `place_from_declarations`. It returned a `Result` which would be `Err` in case of conflicting declarations. But we only actually care about conflicting declarations when we are actually doing type inference on that scope and might emit a diagnostic about it. And in all cases (including that one), we want to otherwise proceed with the union of the declared types, as if there was no conflict. In several cases we were failing to handle the union of declared types in the same way as a normal declared type if there was a declared-types conflict. The `Result` return type made this mistake really easy to make, as we'd match on e.g. `Ok(Place::Type(...))` and do one thing, then match on `Err(...)` and do another, even though really both of those cases should be handled the same. This PR refactors `place_from_declarations` to instead return a struct which always represents the declared type we should use in the same way, as well as carrying the conflicting declared types, if any. This struct has a method to allow us to explicitly ignore the declared-types conflict (which is what we want in most cases), as well as a method to get the declared type and the conflict information, in the case where we want to emit a diagnostic on the conflict. ## Test Plan Existing CI; added a test showing that we now understand a multiply-conditionally-defined method as possibly-unbound. This does trigger issues on a couple new fuzzer seeds, but the issues are just new instances of an already-known (and rarely occurring) problem which I already plan to address in a future PR, so I think it's OK to land as-is. I happened to build this initially on top of https://github.com/astral-sh/ruff/pull/19711, which adds invalid-await diagnostics, so I also updated some invalid-syntax tests to not await on an invalid type, since the purpose of those tests is to check the syntactic location of the `await`, not the validity of the awaited type.
This commit is contained in:
parent
5725c4b17f
commit
e12747a903
10 changed files with 239 additions and 198 deletions
|
@ -56,6 +56,7 @@ def _(
|
|||
def bar() -> None:
|
||||
return None
|
||||
|
||||
async def baz(): ...
|
||||
async def outer(): # avoid unrelated syntax errors on yield, yield from, and await
|
||||
def _(
|
||||
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
|
|||
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
|
||||
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
|
||||
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
|
||||
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
|
||||
l: await baz(), # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
|
||||
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
|
||||
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
|
||||
|
|
|
@ -124,6 +124,9 @@ match obj:
|
|||
## `return`, `yield`, `yield from`, and `await` outside function
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __await__(self): ...
|
||||
|
||||
# error: [invalid-syntax] "`return` statement outside of a function"
|
||||
return
|
||||
|
||||
|
@ -135,11 +138,11 @@ yield from []
|
|||
|
||||
# error: [invalid-syntax] "`await` statement outside of a function"
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await 1
|
||||
await C()
|
||||
|
||||
def f():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await 1
|
||||
await C()
|
||||
```
|
||||
|
||||
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
|
||||
|
@ -330,7 +333,8 @@ async def elements(n):
|
|||
|
||||
def _():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await 1
|
||||
await elements(1)
|
||||
|
||||
# error: [invalid-syntax] "`async for` outside of an asynchronous function"
|
||||
async for _ in elements(1):
|
||||
...
|
||||
|
|
|
@ -40,6 +40,22 @@ class C:
|
|||
reveal_type(C.y) # revealed: Unknown | Literal[1, "abc"]
|
||||
```
|
||||
|
||||
## Possibly unbound in class scope with multiple declarations
|
||||
|
||||
```py
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
class C:
|
||||
if coinflip():
|
||||
x: int = 1
|
||||
elif coinflip():
|
||||
x: str = "abc"
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
reveal_type(C.x) # revealed: int | str
|
||||
```
|
||||
|
||||
## Unbound function local
|
||||
|
||||
An unbound function local that has definitions in the scope does not fall back to globals.
|
||||
|
|
|
@ -472,12 +472,56 @@ pub(crate) fn place_from_declarations<'db>(
|
|||
place_from_declarations_impl(db, declarations, RequiresExplicitReExport::No)
|
||||
}
|
||||
|
||||
pub(crate) type DeclaredTypeAndConflictingTypes<'db> =
|
||||
(TypeAndQualifiers<'db>, Box<indexmap::set::Slice<Type<'db>>>);
|
||||
type DeclaredTypeAndConflictingTypes<'db> = (
|
||||
TypeAndQualifiers<'db>,
|
||||
Option<Box<indexmap::set::Slice<Type<'db>>>>,
|
||||
);
|
||||
|
||||
/// The result of looking up a declared type from declarations; see [`place_from_declarations`].
|
||||
pub(crate) type PlaceFromDeclarationsResult<'db> =
|
||||
Result<PlaceAndQualifiers<'db>, DeclaredTypeAndConflictingTypes<'db>>;
|
||||
pub(crate) struct PlaceFromDeclarationsResult<'db> {
|
||||
place_and_quals: PlaceAndQualifiers<'db>,
|
||||
conflicting_types: Option<Box<indexmap::set::Slice<Type<'db>>>>,
|
||||
}
|
||||
|
||||
impl<'db> PlaceFromDeclarationsResult<'db> {
|
||||
fn conflict(
|
||||
place_and_quals: PlaceAndQualifiers<'db>,
|
||||
conflicting_types: Box<indexmap::set::Slice<Type<'db>>>,
|
||||
) -> Self {
|
||||
PlaceFromDeclarationsResult {
|
||||
place_and_quals,
|
||||
conflicting_types: Some(conflicting_types),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ignore_conflicting_declarations(self) -> PlaceAndQualifiers<'db> {
|
||||
self.place_and_quals
|
||||
}
|
||||
|
||||
pub(crate) fn into_place_and_conflicting_declarations(
|
||||
self,
|
||||
) -> (
|
||||
PlaceAndQualifiers<'db>,
|
||||
Option<Box<indexmap::set::Slice<Type<'db>>>>,
|
||||
) {
|
||||
(self.place_and_quals, self.conflicting_types)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<PlaceAndQualifiers<'db>> for PlaceFromDeclarationsResult<'db> {
|
||||
fn from(place_and_quals: PlaceAndQualifiers<'db>) -> Self {
|
||||
PlaceFromDeclarationsResult {
|
||||
place_and_quals,
|
||||
conflicting_types: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<Place<'db>> for PlaceFromDeclarationsResult<'db> {
|
||||
fn from(place: Place<'db>) -> Self {
|
||||
PlaceFromDeclarationsResult::from(PlaceAndQualifiers::from(place))
|
||||
}
|
||||
}
|
||||
|
||||
/// A type with declaredness information, and a set of type qualifiers.
|
||||
///
|
||||
|
@ -659,7 +703,8 @@ fn place_by_id<'db>(
|
|||
ConsideredDefinitions::AllReachable => use_def.all_reachable_declarations(place_id),
|
||||
};
|
||||
|
||||
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport);
|
||||
let declared = place_from_declarations_impl(db, declarations, requires_explicit_reexport)
|
||||
.ignore_conflicting_declarations();
|
||||
|
||||
let all_considered_bindings = || match considered_definitions {
|
||||
ConsideredDefinitions::EndOfScope => use_def.end_of_scope_bindings(place_id),
|
||||
|
@ -668,53 +713,41 @@ fn place_by_id<'db>(
|
|||
|
||||
// If a symbol is undeclared, but qualified with `typing.Final`, we use the right-hand side
|
||||
// inferred type, without unioning with `Unknown`, because it can not be modified.
|
||||
if let Some(qualifiers) = declared
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(PlaceAndQualifiers::is_bare_final)
|
||||
{
|
||||
if let Some(qualifiers) = declared.is_bare_final() {
|
||||
let bindings = all_considered_bindings();
|
||||
return place_from_bindings_impl(db, bindings, requires_explicit_reexport)
|
||||
.with_qualifiers(qualifiers);
|
||||
}
|
||||
|
||||
// Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the
|
||||
// inferred type.
|
||||
match declared {
|
||||
Ok(PlaceAndQualifiers {
|
||||
// Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the
|
||||
// inferred type.
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Type(Type::Dynamic(DynamicType::Unknown), declaredness),
|
||||
qualifiers,
|
||||
}) if qualifiers.contains(TypeQualifiers::CLASS_VAR) => {
|
||||
} if qualifiers.contains(TypeQualifiers::CLASS_VAR) => {
|
||||
let bindings = all_considered_bindings();
|
||||
match place_from_bindings_impl(db, bindings, requires_explicit_reexport) {
|
||||
Place::Type(inferred, boundness) => {
|
||||
return Place::Type(
|
||||
UnionType::from_elements(db, [Type::unknown(), inferred]),
|
||||
boundness,
|
||||
)
|
||||
.with_qualifiers(qualifiers);
|
||||
}
|
||||
Place::Type(inferred, boundness) => Place::Type(
|
||||
UnionType::from_elements(db, [Type::unknown(), inferred]),
|
||||
boundness,
|
||||
)
|
||||
.with_qualifiers(qualifiers),
|
||||
Place::Unbound => {
|
||||
return Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers);
|
||||
Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match declared {
|
||||
// Place is declared, trust the declared type
|
||||
Ok(
|
||||
place_and_quals @ PlaceAndQualifiers {
|
||||
place: Place::Type(_, Boundness::Bound),
|
||||
qualifiers: _,
|
||||
},
|
||||
) => place_and_quals,
|
||||
place_and_quals @ PlaceAndQualifiers {
|
||||
place: Place::Type(_, Boundness::Bound),
|
||||
qualifiers: _,
|
||||
} => place_and_quals,
|
||||
// Place is possibly declared
|
||||
Ok(PlaceAndQualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Type(declared_ty, Boundness::PossiblyUnbound),
|
||||
qualifiers,
|
||||
}) => {
|
||||
} => {
|
||||
let bindings = all_considered_bindings();
|
||||
let boundness_analysis = bindings.boundness_analysis;
|
||||
let inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
|
||||
|
@ -741,10 +774,10 @@ fn place_by_id<'db>(
|
|||
PlaceAndQualifiers { place, qualifiers }
|
||||
}
|
||||
// Place is undeclared, return the union of `Unknown` with the inferred type
|
||||
Ok(PlaceAndQualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Unbound,
|
||||
qualifiers: _,
|
||||
}) => {
|
||||
} => {
|
||||
let bindings = all_considered_bindings();
|
||||
let boundness_analysis = bindings.boundness_analysis;
|
||||
let mut inferred = place_from_bindings_impl(db, bindings, requires_explicit_reexport);
|
||||
|
@ -786,12 +819,6 @@ fn place_by_id<'db>(
|
|||
.into()
|
||||
}
|
||||
}
|
||||
// Place has conflicting declared types
|
||||
Err((declared, _)) => {
|
||||
// Intentionally ignore conflicting declared types; that's not our problem,
|
||||
// it's the problem of the module we are importing from.
|
||||
Place::bound(declared.inner_type()).with_qualifiers(declared.qualifiers())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (ticket: https://github.com/astral-sh/ruff/issues/14297) Our handling of boundness
|
||||
|
@ -1129,18 +1156,20 @@ impl<'db> DeclaredTypeBuilder<'db> {
|
|||
}
|
||||
|
||||
fn build(mut self) -> DeclaredTypeAndConflictingTypes<'db> {
|
||||
if !self.conflicting_types.is_empty() {
|
||||
let type_and_quals = TypeAndQualifiers::new(self.inner.build(), self.qualifiers);
|
||||
if self.conflicting_types.is_empty() {
|
||||
(type_and_quals, None)
|
||||
} else {
|
||||
self.conflicting_types.insert_before(
|
||||
0,
|
||||
self.first_type
|
||||
.expect("there must be a first type if there are conflicting types"),
|
||||
);
|
||||
(
|
||||
type_and_quals,
|
||||
Some(self.conflicting_types.into_boxed_slice()),
|
||||
)
|
||||
}
|
||||
|
||||
(
|
||||
TypeAndQualifiers::new(self.inner.build(), self.qualifiers),
|
||||
self.conflicting_types.into_boxed_slice(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1203,22 +1232,16 @@ fn place_from_declarations_impl<'db>(
|
|||
);
|
||||
|
||||
if let Some(first) = types.next() {
|
||||
let declared = if let Some(second) = types.next() {
|
||||
let (declared, conflicting) = if let Some(second) = types.next() {
|
||||
let mut builder = DeclaredTypeBuilder::new(db);
|
||||
builder.add(first);
|
||||
builder.add(second);
|
||||
for element in types {
|
||||
builder.add(element);
|
||||
}
|
||||
let (union, conflicting) = builder.build();
|
||||
|
||||
if !conflicting.is_empty() {
|
||||
return Err((union, conflicting));
|
||||
}
|
||||
|
||||
union
|
||||
builder.build()
|
||||
} else {
|
||||
first
|
||||
(first, None)
|
||||
};
|
||||
|
||||
let boundness = match boundness_analysis {
|
||||
|
@ -1244,9 +1267,16 @@ fn place_from_declarations_impl<'db>(
|
|||
},
|
||||
};
|
||||
|
||||
Ok(Place::Type(declared.inner_type(), boundness).with_qualifiers(declared.qualifiers()))
|
||||
let place_and_quals =
|
||||
Place::Type(declared.inner_type(), boundness).with_qualifiers(declared.qualifiers());
|
||||
|
||||
if let Some(conflicting) = conflicting {
|
||||
PlaceFromDeclarationsResult::conflict(place_and_quals, conflicting)
|
||||
} else {
|
||||
place_and_quals.into()
|
||||
}
|
||||
} else {
|
||||
Ok(Place::Unbound.into())
|
||||
Place::Unbound.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1281,31 +1311,32 @@ mod implicit_globals {
|
|||
use crate::semantic_index::{place_table, use_def_map};
|
||||
use crate::types::{KnownClass, Type};
|
||||
|
||||
use super::{Place, PlaceFromDeclarationsResult, place_from_declarations};
|
||||
use super::{Place, place_from_declarations};
|
||||
|
||||
pub(crate) fn module_type_implicit_global_declaration<'db>(
|
||||
db: &'db dyn Db,
|
||||
name: &str,
|
||||
) -> PlaceFromDeclarationsResult<'db> {
|
||||
) -> PlaceAndQualifiers<'db> {
|
||||
if !module_type_symbols(db)
|
||||
.iter()
|
||||
.any(|module_type_member| module_type_member == name)
|
||||
{
|
||||
return Ok(Place::Unbound.into());
|
||||
return Place::Unbound.into();
|
||||
}
|
||||
let Type::ClassLiteral(module_type_class) = KnownClass::ModuleType.to_class_literal(db)
|
||||
else {
|
||||
return Ok(Place::Unbound.into());
|
||||
return Place::Unbound.into();
|
||||
};
|
||||
let module_type_scope = module_type_class.body_scope(db);
|
||||
let place_table = place_table(db, module_type_scope);
|
||||
let Some(symbol_id) = place_table.symbol_id(name) else {
|
||||
return Ok(Place::Unbound.into());
|
||||
return Place::Unbound.into();
|
||||
};
|
||||
place_from_declarations(
|
||||
db,
|
||||
use_def_map(db, module_type_scope).end_of_scope_symbol_declarations(symbol_id),
|
||||
)
|
||||
.ignore_conflicting_declarations()
|
||||
}
|
||||
|
||||
/// Looks up the type of an "implicit global symbol". Returns [`Place::Unbound`] if
|
||||
|
|
|
@ -2275,35 +2275,33 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
let symbol = table.symbol(symbol_id);
|
||||
|
||||
if let Ok(attr) = place_from_declarations(db, declarations) {
|
||||
if attr.is_class_var() {
|
||||
continue;
|
||||
let attr = place_from_declarations(db, declarations).ignore_conflicting_declarations();
|
||||
if attr.is_class_var() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(attr_ty) = attr.place.ignore_possibly_unbound() {
|
||||
let bindings = use_def.end_of_scope_symbol_bindings(symbol_id);
|
||||
let mut default_ty = place_from_bindings(db, bindings).ignore_possibly_unbound();
|
||||
|
||||
default_ty =
|
||||
default_ty.map(|ty| ty.apply_optional_specialization(db, specialization));
|
||||
|
||||
let mut init = true;
|
||||
if let Some(Type::KnownInstance(KnownInstanceType::Field(field))) = default_ty {
|
||||
default_ty = Some(field.default_type(db));
|
||||
init = field.init(db);
|
||||
}
|
||||
|
||||
if let Some(attr_ty) = attr.place.ignore_possibly_unbound() {
|
||||
let bindings = use_def.end_of_scope_symbol_bindings(symbol_id);
|
||||
let mut default_ty =
|
||||
place_from_bindings(db, bindings).ignore_possibly_unbound();
|
||||
|
||||
default_ty =
|
||||
default_ty.map(|ty| ty.apply_optional_specialization(db, specialization));
|
||||
|
||||
let mut init = true;
|
||||
if let Some(Type::KnownInstance(KnownInstanceType::Field(field))) = default_ty {
|
||||
default_ty = Some(field.default_type(db));
|
||||
init = field.init(db);
|
||||
}
|
||||
|
||||
attributes.insert(
|
||||
symbol.name().clone(),
|
||||
Field {
|
||||
declared_ty: attr_ty.apply_optional_specialization(db, specialization),
|
||||
default_ty,
|
||||
init_only: attr.is_init_var(),
|
||||
init,
|
||||
},
|
||||
);
|
||||
}
|
||||
attributes.insert(
|
||||
symbol.name().clone(),
|
||||
Field {
|
||||
declared_ty: attr_ty.apply_optional_specialization(db, specialization),
|
||||
default_ty,
|
||||
init_only: attr.is_init_var(),
|
||||
init,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2735,13 +2733,14 @@ impl<'db> ClassLiteral<'db> {
|
|||
let use_def = use_def_map(db, body_scope);
|
||||
|
||||
let declarations = use_def.end_of_scope_symbol_declarations(symbol_id);
|
||||
let declared_and_qualifiers = place_from_declarations(db, declarations);
|
||||
let declared_and_qualifiers =
|
||||
place_from_declarations(db, declarations).ignore_conflicting_declarations();
|
||||
|
||||
match declared_and_qualifiers {
|
||||
Ok(PlaceAndQualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: mut declared @ Place::Type(declared_ty, declaredness),
|
||||
qualifiers,
|
||||
}) => {
|
||||
} => {
|
||||
// For the purpose of finding instance attributes, ignore `ClassVar`
|
||||
// declarations:
|
||||
if qualifiers.contains(TypeQualifiers::CLASS_VAR) {
|
||||
|
@ -2825,19 +2824,15 @@ impl<'db> ClassLiteral<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(PlaceAndQualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Unbound,
|
||||
qualifiers: _,
|
||||
}) => {
|
||||
} => {
|
||||
// The attribute is not *declared* in the class body. It could still be declared/bound
|
||||
// in a method.
|
||||
|
||||
Self::implicit_attribute(db, body_scope, name, MethodDecorator::None)
|
||||
}
|
||||
Err((declared, _conflicting_declarations)) => {
|
||||
// There are conflicting declarations for this attribute in the class body.
|
||||
Place::bound(declared.inner_type()).with_qualifiers(declared.qualifiers())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This attribute is neither declared nor bound in the class body.
|
||||
|
|
|
@ -191,23 +191,24 @@ pub(crate) fn enum_metadata<'db>(
|
|||
}
|
||||
|
||||
let declarations = use_def_map.end_of_scope_symbol_declarations(symbol_id);
|
||||
let declared = place_from_declarations(db, declarations);
|
||||
let declared =
|
||||
place_from_declarations(db, declarations).ignore_conflicting_declarations();
|
||||
|
||||
match declared {
|
||||
Ok(PlaceAndQualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Type(Type::Dynamic(DynamicType::Unknown), _),
|
||||
qualifiers,
|
||||
}) if qualifiers.contains(TypeQualifiers::FINAL) => {}
|
||||
Ok(PlaceAndQualifiers {
|
||||
} if qualifiers.contains(TypeQualifiers::FINAL) => {}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Unbound,
|
||||
..
|
||||
}) => {
|
||||
} => {
|
||||
// Undeclared attributes are considered members
|
||||
}
|
||||
Ok(PlaceAndQualifiers {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Type(Type::NominalInstance(instance), _),
|
||||
..
|
||||
}) if instance.class(db).is_known(db, KnownClass::Member) => {
|
||||
} if instance.class(db).is_known(db, KnownClass::Member) => {
|
||||
// If the attribute is specifically declared with `enum.member`, it is considered a member
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -35,15 +35,15 @@ pub(crate) fn all_declarations_and_bindings<'db>(
|
|||
.all_end_of_scope_symbol_declarations()
|
||||
.filter_map(move |(symbol_id, declarations)| {
|
||||
place_from_declarations(db, declarations)
|
||||
.ok()
|
||||
.and_then(|result| {
|
||||
result.place.ignore_possibly_unbound().map(|ty| {
|
||||
let symbol = table.symbol(symbol_id);
|
||||
Member {
|
||||
name: symbol.name().clone(),
|
||||
ty,
|
||||
}
|
||||
})
|
||||
.ignore_conflicting_declarations()
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
.map(|ty| {
|
||||
let symbol = table.symbol(symbol_id);
|
||||
Member {
|
||||
name: symbol.name().clone(),
|
||||
ty,
|
||||
}
|
||||
})
|
||||
})
|
||||
.chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
|
||||
|
|
|
@ -1921,85 +1921,75 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
(use_def.declarations_at_binding(binding), true)
|
||||
};
|
||||
|
||||
let (declared_ty, is_modifiable) = place_from_declarations(self.db(), declarations)
|
||||
.and_then(|place_and_quals| {
|
||||
Ok(
|
||||
if matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) {
|
||||
place_and_quals
|
||||
} else if let PlaceExprRef::Symbol(symbol) = place {
|
||||
let symbol_id = place_id.expect_symbol();
|
||||
let (mut place_and_quals, conflicting) = place_from_declarations(self.db(), declarations)
|
||||
.into_place_and_conflicting_declarations();
|
||||
|
||||
if self.skip_non_global_scopes(file_scope_id, symbol_id)
|
||||
|| self.scope.file_scope_id(self.db()).is_global()
|
||||
{
|
||||
let module_type_declarations =
|
||||
module_type_implicit_global_declaration(self.db(), symbol.name())?;
|
||||
place_and_quals.or_fall_back_to(self.db(), || module_type_declarations)
|
||||
} else {
|
||||
place_and_quals
|
||||
}
|
||||
} else {
|
||||
place_and_quals
|
||||
},
|
||||
)
|
||||
})
|
||||
.map(
|
||||
|PlaceAndQualifiers {
|
||||
place: resolved_place,
|
||||
qualifiers,
|
||||
}| {
|
||||
let is_modifiable = !qualifiers.contains(TypeQualifiers::FINAL);
|
||||
if let Some(conflicting) = conflicting {
|
||||
// TODO point out the conflicting declarations in the diagnostic?
|
||||
let place = place_table.place(binding.place(db));
|
||||
if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Conflicting declared types for `{place}`: {}",
|
||||
format_enumeration(conflicting.iter().map(|ty| ty.display(db)))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if resolved_place.is_unbound() && !place_table.place(place_id).is_symbol() {
|
||||
if let AnyNodeRef::ExprAttribute(ast::ExprAttribute {
|
||||
value, attr, ..
|
||||
}) = node
|
||||
{
|
||||
let value_type = self.infer_maybe_standalone_expression(value);
|
||||
if let Place::Type(ty, Boundness::Bound) =
|
||||
value_type.member(db, attr).place
|
||||
{
|
||||
// TODO: also consider qualifiers on the attribute
|
||||
return (ty, is_modifiable);
|
||||
}
|
||||
} else if let AnyNodeRef::ExprSubscript(
|
||||
subscript @ ast::ExprSubscript {
|
||||
value, slice, ctx, ..
|
||||
},
|
||||
) = node
|
||||
{
|
||||
let value_ty = self.infer_expression(value);
|
||||
let slice_ty = self.infer_expression(slice);
|
||||
let result_ty = self.infer_subscript_expression_types(
|
||||
subscript, value_ty, slice_ty, *ctx,
|
||||
);
|
||||
return (result_ty, is_modifiable);
|
||||
}
|
||||
}
|
||||
(
|
||||
resolved_place
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::unknown()),
|
||||
is_modifiable,
|
||||
)
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|(ty, conflicting)| {
|
||||
// TODO point out the conflicting declarations in the diagnostic?
|
||||
let place = place_table.place(binding.place(db));
|
||||
if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Conflicting declared types for `{place}`: {}",
|
||||
format_enumeration(conflicting.iter().map(|ty| ty.display(db)))
|
||||
));
|
||||
// Fall back to implicit module globals for (possibly) unbound names
|
||||
if !matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) {
|
||||
if let PlaceExprRef::Symbol(symbol) = place {
|
||||
let symbol_id = place_id.expect_symbol();
|
||||
|
||||
if self.skip_non_global_scopes(file_scope_id, symbol_id)
|
||||
|| self.scope.file_scope_id(self.db()).is_global()
|
||||
{
|
||||
place_and_quals = place_and_quals.or_fall_back_to(self.db(), || {
|
||||
module_type_implicit_global_declaration(self.db(), symbol.name())
|
||||
});
|
||||
}
|
||||
(
|
||||
ty.inner_type(),
|
||||
!ty.qualifiers.contains(TypeQualifiers::FINAL),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !is_modifiable {
|
||||
let PlaceAndQualifiers {
|
||||
place: resolved_place,
|
||||
qualifiers,
|
||||
} = place_and_quals;
|
||||
|
||||
let unwrap_declared_ty = || {
|
||||
resolved_place
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::unknown())
|
||||
};
|
||||
|
||||
// If the place is unbound and its an attribute or subscript place, fall back to normal
|
||||
// attribute/subscript inference on the root type.
|
||||
let declared_ty = if resolved_place.is_unbound() && !place_table.place(place_id).is_symbol()
|
||||
{
|
||||
if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node {
|
||||
let value_type = self.infer_maybe_standalone_expression(value);
|
||||
if let Place::Type(ty, Boundness::Bound) = value_type.member(db, attr).place {
|
||||
// TODO: also consider qualifiers on the attribute
|
||||
ty
|
||||
} else {
|
||||
unwrap_declared_ty()
|
||||
}
|
||||
} else if let AnyNodeRef::ExprSubscript(
|
||||
subscript @ ast::ExprSubscript {
|
||||
value, slice, ctx, ..
|
||||
},
|
||||
) = node
|
||||
{
|
||||
let value_ty = self.infer_expression(value);
|
||||
let slice_ty = self.infer_expression(slice);
|
||||
self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx)
|
||||
} else {
|
||||
unwrap_declared_ty()
|
||||
}
|
||||
} else {
|
||||
unwrap_declared_ty()
|
||||
};
|
||||
|
||||
if qualifiers.contains(TypeQualifiers::FINAL) {
|
||||
let mut previous_bindings = use_def.bindings_at_definition(binding);
|
||||
|
||||
// An assignment to a local `Final`-qualified symbol is only an error if there are prior bindings
|
||||
|
|
|
@ -538,8 +538,11 @@ fn cached_protocol_interface<'db>(
|
|||
members.extend(
|
||||
use_def_map
|
||||
.all_end_of_scope_symbol_declarations()
|
||||
.flat_map(|(symbol_id, declarations)| {
|
||||
place_from_declarations(db, declarations).map(|place| (symbol_id, place))
|
||||
.map(|(symbol_id, declarations)| {
|
||||
(
|
||||
symbol_id,
|
||||
place_from_declarations(db, declarations).ignore_conflicting_declarations(),
|
||||
)
|
||||
})
|
||||
.filter_map(|(symbol_id, place)| {
|
||||
place
|
||||
|
|
|
@ -153,7 +153,7 @@ class FuzzResult:
|
|||
def fuzz_code(seed: Seed, args: ResolvedCliArgs) -> FuzzResult:
|
||||
"""Return a `FuzzResult` instance describing the fuzzing result from this seed."""
|
||||
# TODO(carljm) remove once we debug the slowness of these seeds
|
||||
skip_check = seed in {120, 160, 335}
|
||||
skip_check = seed in {120, 160, 314, 335}
|
||||
|
||||
code = generate_random_code(seed)
|
||||
bug_found = False
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue