mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:43 +00:00
[red-knot] Symbol API improvements, part 2 (#14276)
## Summary Apart from one small functional change, this is mostly a refactoring of the `Symbol` API: - Rename `as_type` to the more explicit `ignore_possibly_unbound`, no functional change - Remove `unwrap_or_unknown` in favor of the more explicit `.ignore_possibly_unbound().unwrap_or(Type::Unknown)`, no functional change - Consistently call it "possibly unbound" (not "may be unbound") - Rename `replace_unbound_with` to `or_fall_back_to` and properly handle boundness of the fall back. This is the only functional change (did not have any impact on existing tests). relates to: #14022 ## Test Plan New unit tests for `Symbol::or_fall_back_to` --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
fc15d8a3bd
commit
b8a65182dd
4 changed files with 138 additions and 68 deletions
|
@ -277,7 +277,7 @@ impl<'db> UseDefMap<'db> {
|
||||||
|
|
||||||
pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness {
|
pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness {
|
||||||
if self.bindings_by_use[use_id].may_be_unbound() {
|
if self.bindings_by_use[use_id].may_be_unbound() {
|
||||||
Boundness::MayBeUnbound
|
Boundness::PossiblyUnbound
|
||||||
} else {
|
} else {
|
||||||
Boundness::Bound
|
Boundness::Bound
|
||||||
}
|
}
|
||||||
|
@ -292,7 +292,7 @@ impl<'db> UseDefMap<'db> {
|
||||||
|
|
||||||
pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness {
|
pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness {
|
||||||
if self.public_symbols[symbol].may_be_unbound() {
|
if self.public_symbols[symbol].may_be_unbound() {
|
||||||
Boundness::MayBeUnbound
|
Boundness::PossiblyUnbound
|
||||||
} else {
|
} else {
|
||||||
Boundness::Bound
|
Boundness::Bound
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,16 @@ use crate::{
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) enum Boundness {
|
pub(crate) enum Boundness {
|
||||||
Bound,
|
Bound,
|
||||||
MayBeUnbound,
|
PossiblyUnbound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Boundness {
|
||||||
|
pub(crate) fn or(self, other: Boundness) -> Boundness {
|
||||||
|
match (self, other) {
|
||||||
|
(Boundness::Bound, _) | (_, Boundness::Bound) => Boundness::Bound,
|
||||||
|
(Boundness::PossiblyUnbound, Boundness::PossiblyUnbound) => Boundness::PossiblyUnbound,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a symbol lookup, which can either be a (possibly unbound) type
|
/// The result of a symbol lookup, which can either be a (possibly unbound) type
|
||||||
|
@ -17,14 +26,14 @@ pub(crate) enum Boundness {
|
||||||
/// bound = 1
|
/// bound = 1
|
||||||
///
|
///
|
||||||
/// if flag:
|
/// if flag:
|
||||||
/// maybe_unbound = 2
|
/// possibly_unbound = 2
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If we look up symbols in this scope, we would get the following results:
|
/// If we look up symbols in this scope, we would get the following results:
|
||||||
/// ```rs
|
/// ```rs
|
||||||
/// bound: Symbol::Type(Type::IntLiteral(1), Boundness::Bound),
|
/// bound: Symbol::Type(Type::IntLiteral(1), Boundness::Bound),
|
||||||
/// maybe_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::MayBeUnbound),
|
/// possibly_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound),
|
||||||
/// non_existent: Symbol::Unbound,
|
/// non_existent: Symbol::Unbound,
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) enum Symbol<'db> {
|
pub(crate) enum Symbol<'db> {
|
||||||
|
@ -37,21 +46,18 @@ impl<'db> Symbol<'db> {
|
||||||
matches!(self, Symbol::Unbound)
|
matches!(self, Symbol::Unbound)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn may_be_unbound(&self) -> bool {
|
pub(crate) fn possibly_unbound(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Symbol::Type(_, Boundness::MayBeUnbound) | Symbol::Unbound => true,
|
Symbol::Type(_, Boundness::PossiblyUnbound) | Symbol::Unbound => true,
|
||||||
Symbol::Type(_, Boundness::Bound) => false,
|
Symbol::Type(_, Boundness::Bound) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
|
/// Returns the type of the symbol, ignoring possible unboundness.
|
||||||
match self {
|
///
|
||||||
Symbol::Type(ty, _) => *ty,
|
/// If the symbol is *definitely* unbound, this function will return `None`. Otherwise,
|
||||||
Symbol::Unbound => Type::Unknown,
|
/// if there is at least one control-flow path where the symbol is bound, return the type.
|
||||||
}
|
pub(crate) fn ignore_possibly_unbound(&self) -> Option<Type<'db>> {
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_type(&self) -> Option<Type<'db>> {
|
|
||||||
match self {
|
match self {
|
||||||
Symbol::Type(ty, _) => Some(*ty),
|
Symbol::Type(ty, _) => Some(*ty),
|
||||||
Symbol::Unbound => None,
|
Symbol::Unbound => None,
|
||||||
|
@ -61,28 +67,80 @@ impl<'db> Symbol<'db> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn expect_type(self) -> Type<'db> {
|
pub(crate) fn expect_type(self) -> Type<'db> {
|
||||||
self.as_type()
|
self.ignore_possibly_unbound()
|
||||||
.expect("Expected a (possibly unbound) type, not an unbound symbol")
|
.expect("Expected a (possibly unbound) type, not an unbound symbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn replace_unbound_with(
|
pub(crate) fn or_fall_back_to(self, db: &'db dyn Db, fallback: &Symbol<'db>) -> Symbol<'db> {
|
||||||
self,
|
match fallback {
|
||||||
db: &'db dyn Db,
|
Symbol::Type(fallback_ty, fallback_boundness) => match self {
|
||||||
replacement: &Symbol<'db>,
|
Symbol::Type(_, Boundness::Bound) => self,
|
||||||
) -> Symbol<'db> {
|
Symbol::Type(ty, boundness @ Boundness::PossiblyUnbound) => Symbol::Type(
|
||||||
match replacement {
|
UnionType::from_elements(db, [*fallback_ty, ty]),
|
||||||
Symbol::Type(replacement, _) => Symbol::Type(
|
fallback_boundness.or(boundness),
|
||||||
match self {
|
),
|
||||||
Symbol::Type(ty, Boundness::Bound) => ty,
|
Symbol::Unbound => fallback.clone(),
|
||||||
Symbol::Type(ty, Boundness::MayBeUnbound) => {
|
},
|
||||||
UnionType::from_elements(db, [*replacement, ty])
|
|
||||||
}
|
|
||||||
Symbol::Unbound => *replacement,
|
|
||||||
},
|
|
||||||
Boundness::Bound,
|
|
||||||
),
|
|
||||||
Symbol::Unbound => self,
|
Symbol::Unbound => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::types::tests::setup_db;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_symbol_or_fall_back_to() {
|
||||||
|
use Boundness::{Bound, PossiblyUnbound};
|
||||||
|
|
||||||
|
let db = setup_db();
|
||||||
|
let ty1 = Type::IntLiteral(1);
|
||||||
|
let ty2 = Type::IntLiteral(2);
|
||||||
|
|
||||||
|
// Start from an unbound symbol
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Unbound),
|
||||||
|
Symbol::Unbound
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Type(ty1, PossiblyUnbound)),
|
||||||
|
Symbol::Type(ty1, PossiblyUnbound)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Type(ty1, Bound)),
|
||||||
|
Symbol::Type(ty1, Bound)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start from a possibly unbound symbol
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Type(ty1, PossiblyUnbound).or_fall_back_to(&db, &Symbol::Unbound),
|
||||||
|
Symbol::Type(ty1, PossiblyUnbound)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Type(ty1, PossiblyUnbound)
|
||||||
|
.or_fall_back_to(&db, &Symbol::Type(ty2, PossiblyUnbound)),
|
||||||
|
Symbol::Type(UnionType::from_elements(&db, [ty2, ty1]), PossiblyUnbound)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Type(ty1, PossiblyUnbound).or_fall_back_to(&db, &Symbol::Type(ty2, Bound)),
|
||||||
|
Symbol::Type(UnionType::from_elements(&db, [ty2, ty1]), Bound)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start from a definitely bound symbol
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Type(ty1, Bound).or_fall_back_to(&db, &Symbol::Unbound),
|
||||||
|
Symbol::Type(ty1, Bound)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Type(ty1, Bound).or_fall_back_to(&db, &Symbol::Type(ty2, PossiblyUnbound)),
|
||||||
|
Symbol::Type(ty1, Bound)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Symbol::Type(ty1, Bound).or_fall_back_to(&db, &Symbol::Type(ty2, Bound)),
|
||||||
|
Symbol::Type(ty1, Bound)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ fn module_type_symbols<'db>(db: &'db dyn Db) -> smallvec::SmallVec<[ast::name::N
|
||||||
pub(crate) fn global_symbol<'db>(db: &'db dyn Db, file: File, name: &str) -> Symbol<'db> {
|
pub(crate) fn global_symbol<'db>(db: &'db dyn Db, file: File, name: &str) -> Symbol<'db> {
|
||||||
let explicit_symbol = symbol(db, global_scope(db, file), name);
|
let explicit_symbol = symbol(db, global_scope(db, file), name);
|
||||||
|
|
||||||
if !explicit_symbol.may_be_unbound() {
|
if !explicit_symbol.possibly_unbound() {
|
||||||
return explicit_symbol;
|
return explicit_symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ pub(crate) fn global_symbol<'db>(db: &'db dyn Db, file: File, name: &str) -> Sym
|
||||||
// TODO: this should use `.to_instance(db)`. but we don't understand attribute access
|
// TODO: this should use `.to_instance(db)`. but we don't understand attribute access
|
||||||
// on instance types yet.
|
// on instance types yet.
|
||||||
let module_type_member = KnownClass::ModuleType.to_class_literal(db).member(db, name);
|
let module_type_member = KnownClass::ModuleType.to_class_literal(db).member(db, name);
|
||||||
return explicit_symbol.replace_unbound_with(db, &module_type_member);
|
return explicit_symbol.or_fall_back_to(db, &module_type_member);
|
||||||
}
|
}
|
||||||
|
|
||||||
explicit_symbol
|
explicit_symbol
|
||||||
|
@ -1071,11 +1071,11 @@ impl<'db> Type<'db> {
|
||||||
// ignore `__getattr__`. Typeshed has a fake `__getattr__` on `types.ModuleType`
|
// ignore `__getattr__`. Typeshed has a fake `__getattr__` on `types.ModuleType`
|
||||||
// to help out with dynamic imports; we shouldn't use it for `ModuleLiteral` types
|
// to help out with dynamic imports; we shouldn't use it for `ModuleLiteral` types
|
||||||
// where we know exactly which module we're dealing with.
|
// where we know exactly which module we're dealing with.
|
||||||
if name != "__getattr__" && global_lookup.may_be_unbound() {
|
if name != "__getattr__" && global_lookup.possibly_unbound() {
|
||||||
// TODO: this should use `.to_instance()`, but we don't understand instance attribute yet
|
// TODO: this should use `.to_instance()`, but we don't understand instance attribute yet
|
||||||
let module_type_instance_member =
|
let module_type_instance_member =
|
||||||
KnownClass::ModuleType.to_class_literal(db).member(db, name);
|
KnownClass::ModuleType.to_class_literal(db).member(db, name);
|
||||||
global_lookup.replace_unbound_with(db, &module_type_instance_member)
|
global_lookup.or_fall_back_to(db, &module_type_instance_member)
|
||||||
} else {
|
} else {
|
||||||
global_lookup
|
global_lookup
|
||||||
}
|
}
|
||||||
|
@ -1100,17 +1100,17 @@ impl<'db> Type<'db> {
|
||||||
let mut builder = UnionBuilder::new(db);
|
let mut builder = UnionBuilder::new(db);
|
||||||
|
|
||||||
let mut all_unbound = true;
|
let mut all_unbound = true;
|
||||||
let mut may_be_unbound = false;
|
let mut possibly_unbound = false;
|
||||||
for ty in union.elements(db) {
|
for ty in union.elements(db) {
|
||||||
let ty_member = ty.member(db, name);
|
let ty_member = ty.member(db, name);
|
||||||
match ty_member {
|
match ty_member {
|
||||||
Symbol::Unbound => {
|
Symbol::Unbound => {
|
||||||
may_be_unbound = true;
|
possibly_unbound = true;
|
||||||
}
|
}
|
||||||
Symbol::Type(ty_member, member_boundness) => {
|
Symbol::Type(ty_member, member_boundness) => {
|
||||||
// TODO: raise a diagnostic if member_boundness indicates potential unboundness
|
// TODO: raise a diagnostic if member_boundness indicates potential unboundness
|
||||||
if member_boundness == Boundness::MayBeUnbound {
|
if member_boundness == Boundness::PossiblyUnbound {
|
||||||
may_be_unbound = true;
|
possibly_unbound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
all_unbound = false;
|
all_unbound = false;
|
||||||
|
@ -1124,8 +1124,8 @@ impl<'db> Type<'db> {
|
||||||
} else {
|
} else {
|
||||||
Symbol::Type(
|
Symbol::Type(
|
||||||
builder.build(),
|
builder.build(),
|
||||||
if may_be_unbound {
|
if possibly_unbound {
|
||||||
Boundness::MayBeUnbound
|
Boundness::PossiblyUnbound
|
||||||
} else {
|
} else {
|
||||||
Boundness::Bound
|
Boundness::Bound
|
||||||
},
|
},
|
||||||
|
@ -1319,7 +1319,7 @@ impl<'db> Type<'db> {
|
||||||
Symbol::Type(callable_ty, Boundness::Bound) => {
|
Symbol::Type(callable_ty, Boundness::Bound) => {
|
||||||
CallDunderResult::CallOutcome(callable_ty.call(db, arg_types))
|
CallDunderResult::CallOutcome(callable_ty.call(db, arg_types))
|
||||||
}
|
}
|
||||||
Symbol::Type(callable_ty, Boundness::MayBeUnbound) => {
|
Symbol::Type(callable_ty, Boundness::PossiblyUnbound) => {
|
||||||
CallDunderResult::PossiblyUnbound(callable_ty.call(db, arg_types))
|
CallDunderResult::PossiblyUnbound(callable_ty.call(db, arg_types))
|
||||||
}
|
}
|
||||||
Symbol::Unbound => CallDunderResult::MethodNotAvailable,
|
Symbol::Unbound => CallDunderResult::MethodNotAvailable,
|
||||||
|
@ -1625,7 +1625,9 @@ impl<'db> KnownClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
core_module_symbol(db, self.canonical_module(), self.as_str()).unwrap_or_unknown()
|
core_module_symbol(db, self.canonical_module(), self.as_str())
|
||||||
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the module in which we should look up the definition for this class
|
/// Return the module in which we should look up the definition for this class
|
||||||
|
@ -2853,7 +2855,7 @@ impl<'db> TupleType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::program::{Program, SearchPathSettings};
|
use crate::program::{Program, SearchPathSettings};
|
||||||
|
@ -2874,7 +2876,7 @@ mod tests {
|
||||||
assert_eq!(size_of::<Type>(), 16);
|
assert_eq!(size_of::<Type>(), 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_db() -> TestDb {
|
pub(crate) fn setup_db() -> TestDb {
|
||||||
let db = TestDb::new();
|
let db = TestDb::new();
|
||||||
|
|
||||||
let src_root = SystemPathBuf::from("/src");
|
let src_root = SystemPathBuf::from("/src");
|
||||||
|
|
|
@ -1237,7 +1237,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
}
|
}
|
||||||
(Symbol::Type(enter_ty, enter_boundness), exit) => {
|
(Symbol::Type(enter_ty, enter_boundness), exit) => {
|
||||||
if enter_boundness == Boundness::MayBeUnbound {
|
if enter_boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
"invalid-context-manager",
|
||||||
|
@ -1276,7 +1276,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Symbol::Type(exit_ty, exit_boundness) => {
|
Symbol::Type(exit_ty, exit_boundness) => {
|
||||||
// TODO: Use the `exit_ty` to determine if any raised exception is suppressed.
|
// TODO: Use the `exit_ty` to determine if any raised exception is suppressed.
|
||||||
|
|
||||||
if exit_boundness == Boundness::MayBeUnbound {
|
if exit_boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
context_expression.into(),
|
context_expression.into(),
|
||||||
"invalid-context-manager",
|
"invalid-context-manager",
|
||||||
|
@ -1340,7 +1340,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// TODO should infer `ExceptionGroup` if all caught exceptions
|
// TODO should infer `ExceptionGroup` if all caught exceptions
|
||||||
// are subclasses of `Exception` --Alex
|
// are subclasses of `Exception` --Alex
|
||||||
builtins_symbol(self.db, "BaseExceptionGroup")
|
builtins_symbol(self.db, "BaseExceptionGroup")
|
||||||
.unwrap_or_unknown()
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown)
|
||||||
.to_instance(self.db)
|
.to_instance(self.db)
|
||||||
} else {
|
} else {
|
||||||
// TODO: anything that's a consistent subtype of
|
// TODO: anything that's a consistent subtype of
|
||||||
|
@ -1710,7 +1711,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
return match boundness {
|
return match boundness {
|
||||||
Boundness::Bound => augmented_return_ty,
|
Boundness::Bound => augmented_return_ty,
|
||||||
Boundness::MayBeUnbound => {
|
Boundness::PossiblyUnbound => {
|
||||||
let left_ty = target_type;
|
let left_ty = target_type;
|
||||||
let right_ty = value_type;
|
let right_ty = value_type;
|
||||||
|
|
||||||
|
@ -2008,10 +2009,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
} = alias;
|
} = alias;
|
||||||
|
|
||||||
// For possibly-unbound names, just eliminate Unbound from the type; we
|
// For possibly-unbound names, just eliminate Unbound from the type; we
|
||||||
// must be in a bound path. TODO diagnostic for maybe-unbound import?
|
// must be in a bound path. TODO diagnostic for possibly-unbound import?
|
||||||
module_ty
|
module_ty
|
||||||
.member(self.db, &ast::name::Name::new(&name.id))
|
.member(self.db, &ast::name::Name::new(&name.id))
|
||||||
.as_type()
|
.ignore_possibly_unbound()
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
AnyNodeRef::Alias(alias),
|
AnyNodeRef::Alias(alias),
|
||||||
|
@ -2186,7 +2187,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db)),
|
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db)),
|
||||||
ast::Number::Float(_) => KnownClass::Float.to_instance(self.db),
|
ast::Number::Float(_) => KnownClass::Float.to_instance(self.db),
|
||||||
ast::Number::Complex { .. } => builtins_symbol(self.db, "complex")
|
ast::Number::Complex { .. } => builtins_symbol(self.db, "complex")
|
||||||
.unwrap_or_unknown()
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown)
|
||||||
.to_instance(self.db),
|
.to_instance(self.db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2265,7 +2267,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
&mut self,
|
&mut self,
|
||||||
_literal: &ast::ExprEllipsisLiteral,
|
_literal: &ast::ExprEllipsisLiteral,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
builtins_symbol(self.db, "Ellipsis").unwrap_or_unknown()
|
builtins_symbol(self.db, "Ellipsis")
|
||||||
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
|
fn infer_tuple_expression(&mut self, tuple: &ast::ExprTuple) -> Type<'db> {
|
||||||
|
@ -2688,21 +2692,21 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallback to builtins (without infinite recursion if we're already in builtins.)
|
// Fallback to builtins (without infinite recursion if we're already in builtins.)
|
||||||
if global_symbol.may_be_unbound()
|
if global_symbol.possibly_unbound()
|
||||||
&& Some(self.scope()) != builtins_module_scope(self.db)
|
&& Some(self.scope()) != builtins_module_scope(self.db)
|
||||||
{
|
{
|
||||||
let mut symbol = builtins_symbol(self.db, name);
|
let mut builtins_symbol = builtins_symbol(self.db, name);
|
||||||
if symbol.is_unbound() && name == "reveal_type" {
|
if builtins_symbol.is_unbound() && name == "reveal_type" {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
name_node.into(),
|
name_node.into(),
|
||||||
"undefined-reveal",
|
"undefined-reveal",
|
||||||
format_args!(
|
format_args!(
|
||||||
"`reveal_type` used without importing it; this is allowed for debugging convenience but will fail at runtime"),
|
"`reveal_type` used without importing it; this is allowed for debugging convenience but will fail at runtime"),
|
||||||
);
|
);
|
||||||
symbol = typing_extensions_symbol(self.db, name);
|
builtins_symbol = typing_extensions_symbol(self.db, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
global_symbol.replace_unbound_with(self.db, &symbol)
|
global_symbol.or_fall_back_to(self.db, &builtins_symbol)
|
||||||
} else {
|
} else {
|
||||||
global_symbol
|
global_symbol
|
||||||
}
|
}
|
||||||
|
@ -2742,10 +2746,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
let bindings_ty = bindings_ty(self.db, definitions);
|
let bindings_ty = bindings_ty(self.db, definitions);
|
||||||
|
|
||||||
if boundness == Boundness::MayBeUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
match self.lookup_name(name) {
|
match self.lookup_name(name) {
|
||||||
Symbol::Type(looked_up_ty, looked_up_boundness) => {
|
Symbol::Type(looked_up_ty, looked_up_boundness) => {
|
||||||
if looked_up_boundness == Boundness::MayBeUnbound {
|
if looked_up_boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add_possibly_unresolved_reference(name);
|
self.diagnostics.add_possibly_unresolved_reference(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2787,7 +2791,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let value_ty = self.infer_expression(value);
|
let value_ty = self.infer_expression(value);
|
||||||
value_ty
|
value_ty
|
||||||
.member(self.db, &Name::new(&attr.id))
|
.member(self.db, &Name::new(&attr.id))
|
||||||
.unwrap_or_unknown()
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
|
fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
|
||||||
|
@ -3850,7 +3855,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
match value_meta_ty.member(self.db, "__getitem__") {
|
match value_meta_ty.member(self.db, "__getitem__") {
|
||||||
Symbol::Unbound => {}
|
Symbol::Unbound => {}
|
||||||
Symbol::Type(dunder_getitem_method, boundness) => {
|
Symbol::Type(dunder_getitem_method, boundness) => {
|
||||||
if boundness == Boundness::MayBeUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
"call-possibly-unbound-method",
|
"call-possibly-unbound-method",
|
||||||
|
@ -3894,7 +3899,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
match dunder_class_getitem_method {
|
match dunder_class_getitem_method {
|
||||||
Symbol::Unbound => {}
|
Symbol::Unbound => {}
|
||||||
Symbol::Type(ty, boundness) => {
|
Symbol::Type(ty, boundness) => {
|
||||||
if boundness == Boundness::MayBeUnbound {
|
if boundness == Boundness::PossiblyUnbound {
|
||||||
self.diagnostics.add(
|
self.diagnostics.add(
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
"call-possibly-unbound-method",
|
"call-possibly-unbound-method",
|
||||||
|
@ -4383,7 +4388,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||||
let value_ty = self.infer_expression(value);
|
let value_ty = self.infer_expression(value);
|
||||||
// TODO: Check that value type is enum otherwise return None
|
// TODO: Check that value type is enum otherwise return None
|
||||||
value_ty.member(self.db, &attr.id).unwrap_or_unknown()
|
value_ty
|
||||||
|
.member(self.db, &attr.id)
|
||||||
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
ast::Expr::NoneLiteral(_) => Type::none(self.db),
|
ast::Expr::NoneLiteral(_) => Type::none(self.db),
|
||||||
// for negative and positive numbers
|
// for negative and positive numbers
|
||||||
|
@ -4740,7 +4748,9 @@ mod tests {
|
||||||
assert_eq!(scope.name(db), *expected_scope_name);
|
assert_eq!(scope.name(db), *expected_scope_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ty = symbol(db, scope, symbol_name).unwrap_or_unknown();
|
let ty = symbol(db, scope, symbol_name)
|
||||||
|
.ignore_possibly_unbound()
|
||||||
|
.unwrap_or(Type::Unknown);
|
||||||
assert_eq!(ty.display(db).to_string(), expected);
|
assert_eq!(ty.display(db).to_string(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue