mirror of
https://github.com/astral-sh/ruff.git
synced 2025-12-23 09:19:58 +00:00
store unwidened
This commit is contained in:
parent
be2b515f78
commit
07ba2f4f22
5 changed files with 52 additions and 55 deletions
|
|
@ -76,6 +76,16 @@ pub(crate) enum Widening {
|
|||
WithUnknown,
|
||||
}
|
||||
|
||||
impl Widening {
|
||||
/// Apply widening to the type if this is `WithUnknown`.
|
||||
pub(crate) fn apply_if_needed<'db>(self, db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {
|
||||
match self {
|
||||
Self::None => ty,
|
||||
Self::WithUnknown => UnionType::from_elements(db, [Type::unknown(), ty]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a place lookup, which can either be a (possibly undefined) type
|
||||
/// or a completely undefined place.
|
||||
///
|
||||
|
|
@ -152,9 +162,6 @@ impl<'db> Place<'db> {
|
|||
///
|
||||
/// If the place is *definitely* undefined, this function will return `None`. Otherwise,
|
||||
/// if there is at least one control-flow path where the place is defined, return the type.
|
||||
///
|
||||
/// Note: This returns the raw type without applying widening. Use `widened_type()` if you
|
||||
/// need the type with the Unknown union applied for undeclared public symbols.
|
||||
pub(crate) fn ignore_possibly_undefined(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Place::Defined(ty, _, _, _) => Some(*ty),
|
||||
|
|
@ -162,6 +169,17 @@ impl<'db> Place<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the type of the place without widening applied.
|
||||
///
|
||||
/// The stored type is always the unwidened type. Widening (union with `Unknown`)
|
||||
/// is applied lazily when converting to `LookupResult`.
|
||||
pub(crate) fn unwidened_type(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Place::Defined(ty, _, _, _) => Some(*ty),
|
||||
Place::Undefined => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[track_caller]
|
||||
pub(crate) fn expect_type(self) -> Type<'db> {
|
||||
|
|
@ -266,7 +284,7 @@ impl<'db> LookupError<'db> {
|
|||
db: &'db dyn Db,
|
||||
fallback: PlaceAndQualifiers<'db>,
|
||||
) -> LookupResult<'db> {
|
||||
let fallback = fallback.into_lookup_result();
|
||||
let fallback = fallback.into_lookup_result(db);
|
||||
match (&self, &fallback) {
|
||||
(LookupError::Undefined(_), _) => fallback,
|
||||
(LookupError::PossiblyUndefined { .. }, Err(LookupError::Undefined(_))) => Err(self),
|
||||
|
|
@ -687,18 +705,27 @@ impl<'db> PlaceAndQualifiers<'db> {
|
|||
/// Transform place and qualifiers into a [`LookupResult`],
|
||||
/// a [`Result`] type in which the `Ok` variant represents a definitely defined place
|
||||
/// and the `Err` variant represents a place that is either definitely or possibly undefined.
|
||||
pub(crate) fn into_lookup_result(self) -> LookupResult<'db> {
|
||||
///
|
||||
/// For places marked with `Widening::WithUnknown`, this applies the gradual typing guarantee
|
||||
/// by creating a union with `Unknown`.
|
||||
pub(crate) fn into_lookup_result(self, db: &'db dyn Db) -> LookupResult<'db> {
|
||||
match self {
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, origin, Definedness::AlwaysDefined, _),
|
||||
place: Place::Defined(ty, origin, Definedness::AlwaysDefined, widening),
|
||||
qualifiers,
|
||||
} => Ok(TypeAndQualifiers::new(ty, origin, qualifiers)),
|
||||
} => {
|
||||
let ty = widening.apply_if_needed(db, ty);
|
||||
Ok(TypeAndQualifiers::new(ty, origin, qualifiers))
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Defined(ty, origin, Definedness::PossiblyUndefined, _),
|
||||
place: Place::Defined(ty, origin, Definedness::PossiblyUndefined, widening),
|
||||
qualifiers,
|
||||
} => Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new(
|
||||
ty, origin, qualifiers,
|
||||
))),
|
||||
} => {
|
||||
let ty = widening.apply_if_needed(db, ty);
|
||||
Err(LookupError::PossiblyUndefined(TypeAndQualifiers::new(
|
||||
ty, origin, qualifiers,
|
||||
)))
|
||||
}
|
||||
PlaceAndQualifiers {
|
||||
place: Place::Undefined,
|
||||
qualifiers,
|
||||
|
|
@ -706,17 +733,18 @@ impl<'db> PlaceAndQualifiers<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Safely unwrap the place and the qualifiers into a [`TypeQualifiers`].
|
||||
/// Safely unwrap the place and the qualifiers into a [`TypeAndQualifiers`].
|
||||
///
|
||||
/// If the place is definitely unbound or possibly unbound, it will be transformed into a
|
||||
/// [`LookupError`] and `diagnostic_fn` will be applied to the error value before returning
|
||||
/// the result of `diagnostic_fn` (which will be a [`TypeQualifiers`]). This allows the caller
|
||||
/// the result of `diagnostic_fn` (which will be a [`TypeAndQualifiers`]). This allows the caller
|
||||
/// to ensure that a diagnostic is emitted if the place is possibly or definitely unbound.
|
||||
pub(crate) fn unwrap_with_diagnostic(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
diagnostic_fn: impl FnOnce(LookupError<'db>) -> TypeAndQualifiers<'db>,
|
||||
) -> TypeAndQualifiers<'db> {
|
||||
self.into_lookup_result().unwrap_or_else(diagnostic_fn)
|
||||
self.into_lookup_result(db).unwrap_or_else(diagnostic_fn)
|
||||
}
|
||||
|
||||
/// Fallback (partially or fully) to another place if `self` is partially or fully unbound.
|
||||
|
|
@ -735,7 +763,7 @@ impl<'db> PlaceAndQualifiers<'db> {
|
|||
db: &'db dyn Db,
|
||||
fallback_fn: impl FnOnce() -> PlaceAndQualifiers<'db>,
|
||||
) -> Self {
|
||||
self.into_lookup_result()
|
||||
self.into_lookup_result(db)
|
||||
.or_else(|lookup_error| lookup_error.or_fall_back_to(db, fallback_fn()))
|
||||
.into()
|
||||
}
|
||||
|
|
@ -988,13 +1016,10 @@ pub(crate) fn place_by_id<'db>(
|
|||
{
|
||||
inferred.into()
|
||||
} else {
|
||||
// Gradual typing guarantee: Union with `Unknown` for undeclared public symbols.
|
||||
// This allows external code to assign any type to these symbols.
|
||||
// We also mark the widening flag to identify this type as widened.
|
||||
inferred
|
||||
.map_type(|ty| UnionType::from_elements(db, [Type::unknown(), ty]))
|
||||
.with_widening(Widening::WithUnknown)
|
||||
.into()
|
||||
// Gradual typing guarantee: Mark undeclared public symbols for widening.
|
||||
// The actual union with `Unknown` is applied lazily when converting to
|
||||
// LookupResult via `into_lookup_result`.
|
||||
inferred.with_widening(Widening::WithUnknown).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11780,7 +11780,7 @@ impl<'db> BoolError<'db> {
|
|||
);
|
||||
if let Some((func_span, parameter_span)) = not_boolable_type
|
||||
.member(context.db(), "__bool__")
|
||||
.into_lookup_result()
|
||||
.into_lookup_result(context.db())
|
||||
.ok()
|
||||
.and_then(|quals| quals.inner_type().parameter_span(context.db(), None))
|
||||
{
|
||||
|
|
@ -11808,7 +11808,7 @@ impl<'db> BoolError<'db> {
|
|||
);
|
||||
if let Some((func_span, return_type_span)) = not_boolable_type
|
||||
.member(context.db(), "__bool__")
|
||||
.into_lookup_result()
|
||||
.into_lookup_result(context.db())
|
||||
.ok()
|
||||
.and_then(|quals| quals.inner_type().function_spans(context.db()))
|
||||
.and_then(|spans| Some((spans.name, spans.return_type?)))
|
||||
|
|
|
|||
|
|
@ -2361,7 +2361,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
// For enum classes, `nonmember(value)` creates a non-member attribute.
|
||||
// At runtime, the enum metaclass unwraps the value, so accessing the attribute
|
||||
// returns the inner value, not the `nonmember` wrapper.
|
||||
if let Some(ty) = member.inner.place.ignore_possibly_undefined() {
|
||||
if let Some(ty) = member.inner.place.unwidened_type() {
|
||||
if let Some(value_ty) = try_unwrap_nonmember_value(db, ty) {
|
||||
if is_enum_class_by_inheritance(db, self) {
|
||||
return Member::definitely_declared(value_ty);
|
||||
|
|
|
|||
|
|
@ -319,34 +319,6 @@ pub(crate) fn try_unwrap_nonmember_value<'db>(db: &'db dyn Db, ty: Type<'db>) ->
|
|||
.unwrap_or(Type::unknown()),
|
||||
)
|
||||
}
|
||||
Type::Union(union) => {
|
||||
// TODO: This is a hack. The proper fix is to avoid unioning Unknown from
|
||||
// declarations into Place when we have concrete bindings.
|
||||
//
|
||||
// For now, we filter out Unknown and expect exactly one nonmember type
|
||||
// to remain. If there are other non-Unknown types mixed in, we bail out.
|
||||
let mut non_unknown = union.elements(db).iter().filter(|elem| !elem.is_unknown());
|
||||
|
||||
let first = non_unknown.next()?;
|
||||
|
||||
// Ensure there's exactly one non-Unknown element.
|
||||
if non_unknown.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Type::NominalInstance(instance) = first {
|
||||
if instance.has_known_class(db, KnownClass::Nonmember) {
|
||||
return Some(
|
||||
first
|
||||
.member(db, "value")
|
||||
.place
|
||||
.ignore_possibly_undefined()
|
||||
.unwrap_or(Type::unknown()),
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9029,7 +9029,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
let ty =
|
||||
resolved_after_fallback.unwrap_with_diagnostic(|lookup_error| match lookup_error {
|
||||
resolved_after_fallback.unwrap_with_diagnostic(db, |lookup_error| match lookup_error {
|
||||
LookupError::Undefined(qualifiers) => {
|
||||
self.report_unresolved_reference(name_node);
|
||||
TypeAndQualifiers::new(Type::unknown(), TypeOrigin::Inferred, qualifiers)
|
||||
|
|
@ -9570,7 +9570,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
});
|
||||
|
||||
let attr_name = &attr.id;
|
||||
let resolved_type = fallback_place.unwrap_with_diagnostic(|lookup_err| match lookup_err {
|
||||
let resolved_type = fallback_place.unwrap_with_diagnostic(db, |lookup_err| match lookup_err {
|
||||
LookupError::Undefined(_) => {
|
||||
let fallback = || {
|
||||
TypeAndQualifiers::new(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue