[ty] Create separate FunctionLiteral and FunctionType types (#18360)

This updates our representation of functions to more closely match our
representation of classes.

The new `OverloadLiteral` and `FunctionLiteral` classes represent a
function definition in the AST. If a function is generic, this is
unspecialized. `FunctionType` has been updated to represent a function
type, which is specialized if the function is generic. (These names are
chosen to match `ClassLiteral` and `ClassType` on the class side.)

This PR does not add a separate `Type` variant for `FunctionLiteral`.
Maybe we should? Possibly as a follow-on PR?

Part of https://github.com/astral-sh/ty/issues/462

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Douglas Creager 2025-06-03 10:59:31 -04:00 committed by GitHub
parent 8d98c601d8
commit 2c3b3d3230
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1280 additions and 1021 deletions

View file

@ -2,7 +2,6 @@ use infer::nearest_enclosing_class;
use itertools::Either;
use std::slice::Iter;
use std::str::FromStr;
use bitflags::bitflags;
use call::{CallDunderError, CallError, CallErrorKind};
@ -14,7 +13,7 @@ use diagnostic::{
use ruff_db::diagnostic::{
Annotation, Severity, Span, SubDiagnostic, create_semantic_syntax_diagnostic,
};
use ruff_db::files::{File, FileRange};
use ruff_db::files::File;
use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::{Ranged, TextRange};
@ -28,23 +27,23 @@ pub(crate) use self::infer::{
infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
infer_scope_types,
};
pub(crate) use self::narrow::ClassInfoConstraintFunction;
pub(crate) use self::signatures::{CallableSignature, Signature};
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, file_to_module, resolve_module};
use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId};
use crate::module_resolver::{KnownModule, resolve_module};
use crate::semantic_index::ast_ids::HasScopedExpressionId;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::ScopeId;
use crate::semantic_index::{imported_modules, semantic_index};
use crate::suppression::check_suppressions;
use crate::symbol::{
Boundness, Symbol, SymbolAndQualifiers, imported_symbol, symbol_from_bindings,
};
use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers, imported_symbol};
use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding};
pub(crate) use crate::types::class_base::ClassBase;
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
};
use crate::types::generics::{GenericContext, PartialSpecialization, Specialization};
use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator};
@ -64,6 +63,7 @@ mod class_base;
mod context;
mod diagnostic;
mod display;
mod function;
mod generics;
mod ide_support;
mod infer;
@ -432,26 +432,6 @@ impl From<DataclassTransformerParams> for DataclassParams {
}
}
bitflags! {
/// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the
/// arguments that were passed in. For the precise meaning of the fields, see [1].
///
/// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub struct DataclassTransformerParams: u8 {
const EQ_DEFAULT = 0b0000_0001;
const ORDER_DEFAULT = 0b0000_0010;
const KW_ONLY_DEFAULT = 0b0000_0100;
const FROZEN_DEFAULT = 0b0000_1000;
}
}
impl Default for DataclassTransformerParams {
fn default() -> Self {
Self::EQ_DEFAULT
}
}
/// Representation of a type: a set of possible values at runtime.
///
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
@ -7040,710 +7020,6 @@ impl From<bool> for Truthiness {
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)]
pub struct FunctionDecorators: u8 {
/// `@classmethod`
const CLASSMETHOD = 1 << 0;
/// `@typing.no_type_check`
const NO_TYPE_CHECK = 1 << 1;
/// `@typing.overload`
const OVERLOAD = 1 << 2;
/// `@abc.abstractmethod`
const ABSTRACT_METHOD = 1 << 3;
/// `@typing.final`
const FINAL = 1 << 4;
/// `@typing.override`
const OVERRIDE = 1 << 6;
}
}
/// A function signature, which optionally includes an implementation signature if the function is
/// overloaded.
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
pub(crate) struct FunctionSignature<'db> {
pub(crate) overloads: CallableSignature<'db>,
pub(crate) implementation: Option<Signature<'db>>,
}
impl<'db> FunctionSignature<'db> {
/// Returns the "bottom" signature (subtype of all fully-static signatures.)
pub(crate) fn bottom(db: &'db dyn Db) -> Self {
FunctionSignature {
overloads: CallableSignature::single(Signature::bottom(db)),
implementation: None,
}
}
}
/// An overloaded function.
///
/// This is created by the [`to_overloaded`] method on [`FunctionType`].
///
/// [`to_overloaded`]: FunctionType::to_overloaded
#[derive(Debug, PartialEq, Eq, salsa::Update)]
struct OverloadedFunction<'db> {
/// The overloads of this function.
overloads: Vec<FunctionType<'db>>,
/// The implementation of this overloaded function, if any.
implementation: Option<FunctionType<'db>>,
}
impl<'db> OverloadedFunction<'db> {
/// Returns an iterator over all overloads and the implementation, in that order.
fn all(&self) -> impl Iterator<Item = FunctionType<'db>> + '_ {
self.overloads.iter().copied().chain(self.implementation)
}
}
/// # Ordering
/// Ordering is based on the function type's salsa-assigned id and not on its values.
/// The id may change between runs, or when the function type was garbage collected and recreated.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct FunctionType<'db> {
/// Name of the function at definition.
#[returns(ref)]
pub name: ast::name::Name,
/// Is this a function that we special-case somehow? If so, which one?
known: Option<KnownFunction>,
/// The scope that's created by the function, in which the function body is evaluated.
body_scope: ScopeId<'db>,
/// A set of special decorators that were applied to this function
decorators: FunctionDecorators,
/// The arguments to `dataclass_transformer`, if this function was annotated
/// with `@dataclass_transformer(...)`.
dataclass_transformer_params: Option<DataclassTransformerParams>,
/// The inherited generic context, if this function is a class method being used to infer the
/// specialization of its generic class. If the method is itself generic, this is in addition
/// to its own generic context.
inherited_generic_context: Option<GenericContext<'db>>,
/// Type mappings that should be applied to the function's parameter and return types.
type_mappings: Box<[TypeMapping<'db, 'db>]>,
}
#[salsa::tracked]
impl<'db> FunctionType<'db> {
/// Returns the [`File`] in which this function is defined.
pub(crate) fn file(self, db: &'db dyn Db) -> File {
// NOTE: Do not use `self.definition(db).file(db)` here, as that could create a
// cross-module dependency on the full AST.
self.body_scope(db).file(db)
}
pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.decorators(db).contains(decorator)
}
/// Convert the `FunctionType` into a [`Type::Callable`].
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
Type::Callable(CallableType::new(
db,
self.signature(db).overloads.clone(),
false,
))
}
/// Convert the `FunctionType` into a [`Type::BoundMethod`].
pub(crate) fn into_bound_method_type(
self,
db: &'db dyn Db,
self_instance: Type<'db>,
) -> Type<'db> {
Type::BoundMethod(BoundMethodType::new(db, self, self_instance))
}
/// Returns the AST node for this function.
pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef {
debug_assert_eq!(
file,
self.file(db),
"FunctionType::node() must be called with the same file as the one where \
the function is defined."
);
self.body_scope(db).node(db).expect_function()
}
/// Returns the [`FileRange`] of the function's name.
pub fn focus_range(self, db: &dyn Db) -> FileRange {
FileRange::new(
self.file(db),
self.body_scope(db).node(db).expect_function().name.range,
)
}
pub fn full_range(self, db: &dyn Db) -> FileRange {
FileRange::new(
self.file(db),
self.body_scope(db).node(db).expect_function().range,
)
}
/// Returns the [`Definition`] of this function.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let body_scope = self.body_scope(db);
let index = semantic_index(db, body_scope.file(db));
index.expect_single_definition(body_scope.node(db).expect_function())
}
/// Typed externally-visible signature for this function.
///
/// This is the signature as seen by external callers, possibly modified by decorators and/or
/// overloaded.
///
/// ## Why is this a salsa query?
///
/// This is a salsa query to short-circuit the invalidation
/// when the function's AST node changes.
///
/// Were this not a salsa query, then the calling query
/// would depend on the function's AST and rerun for every change in that file.
#[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)]
pub(crate) fn signature(self, db: &'db dyn Db) -> FunctionSignature<'db> {
let inherited_generic_context = self.inherited_generic_context(db);
let type_mappings = self.type_mappings(db);
if let Some(overloaded) = self.to_overloaded(db) {
FunctionSignature {
overloads: CallableSignature::from_overloads(
overloaded.overloads.iter().copied().map(|overload| {
type_mappings.iter().fold(
overload.internal_signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
)
}),
),
implementation: overloaded.implementation.map(|implementation| {
type_mappings.iter().fold(
implementation.internal_signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
)
}),
}
} else {
FunctionSignature {
overloads: CallableSignature::single(type_mappings.iter().fold(
self.internal_signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
)),
implementation: None,
}
}
}
/// Typed internally-visible signature for this function.
///
/// This represents the annotations on the function itself, unmodified by decorators and
/// overloads.
///
/// These are the parameter and return types that should be used for type checking the body of
/// the function.
///
/// Don't call this when checking any other file; only when type-checking the function body
/// scope.
fn internal_signature(
self,
db: &'db dyn Db,
inherited_generic_context: Option<GenericContext<'db>>,
) -> Signature<'db> {
let scope = self.body_scope(db);
let function_stmt_node = scope.node(db).expect_function();
let definition = self.definition(db);
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, type_params)
});
Signature::from_function(
db,
generic_context,
inherited_generic_context,
definition,
function_stmt_node,
)
}
pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool {
self.known(db) == Some(known_function)
}
fn with_dataclass_transformer_params(
self,
db: &'db dyn Db,
params: DataclassTransformerParams,
) -> Self {
Self::new(
db,
self.name(db).clone(),
self.known(db),
self.body_scope(db),
self.decorators(db),
Some(params),
self.inherited_generic_context(db),
self.type_mappings(db),
)
}
fn with_inherited_generic_context(
self,
db: &'db dyn Db,
inherited_generic_context: GenericContext<'db>,
) -> Self {
// A function cannot inherit more than one generic context from its containing class.
debug_assert!(self.inherited_generic_context(db).is_none());
Self::new(
db,
self.name(db).clone(),
self.known(db),
self.body_scope(db),
self.decorators(db),
self.dataclass_transformer_params(db),
Some(inherited_generic_context),
self.type_mappings(db),
)
}
fn with_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
let type_mappings: Box<[_]> = self
.type_mappings(db)
.iter()
.cloned()
.chain(std::iter::once(type_mapping.to_owned()))
.collect();
Self::new(
db,
self.name(db).clone(),
self.known(db),
self.body_scope(db),
self.decorators(db),
self.dataclass_transformer_params(db),
self.inherited_generic_context(db),
type_mappings,
)
}
fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) {
let signatures = self.signature(db);
for signature in &signatures.overloads {
signature.find_legacy_typevars(db, typevars);
}
}
/// Returns `self` as [`OverloadedFunction`] if it is overloaded, [`None`] otherwise.
///
/// ## Note
///
/// The way this method works only allows us to "see" the overloads that are defined before
/// this function definition. This is because the semantic model records a use for each
/// function on the name node which is used to get the previous function definition with the
/// same name. This means that [`OverloadedFunction`] would only include the functions that
/// comes before this function definition. Consider the following example:
///
/// ```py
/// from typing import overload
///
/// @overload
/// def foo() -> None: ...
/// @overload
/// def foo(x: int) -> int: ...
/// def foo(x: int | None) -> int | None:
/// return x
/// ```
///
/// Here, when the `to_overloaded` method is invoked on the
/// 1. first `foo` definition, it would only contain a single overload which is itself and no
/// implementation
/// 2. second `foo` definition, it would contain both overloads and still no implementation
/// 3. third `foo` definition, it would contain both overloads and the implementation which is
/// itself
#[salsa::tracked(returns(as_ref))]
fn to_overloaded(self, db: &'db dyn Db) -> Option<OverloadedFunction<'db>> {
let mut current = self;
let mut overloads = vec![];
loop {
// The semantic model records a use for each function on the name node. This is used
// here to get the previous function definition with the same name.
let scope = current.definition(db).scope(db);
let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db));
let use_id = current
.body_scope(db)
.node(db)
.expect_function()
.name
.scoped_use_id(db, scope);
let Symbol::Type(Type::FunctionLiteral(previous), Boundness::Bound) =
symbol_from_bindings(db, use_def.bindings_at_use(use_id))
else {
break;
};
if previous.has_known_decorator(db, FunctionDecorators::OVERLOAD) {
overloads.push(previous);
} else {
break;
}
current = previous;
}
// Overloads are inserted in reverse order, from bottom to top.
overloads.reverse();
let implementation = if self.has_known_decorator(db, FunctionDecorators::OVERLOAD) {
overloads.push(self);
None
} else {
Some(self)
};
if overloads.is_empty() {
None
} else {
Some(OverloadedFunction {
overloads,
implementation,
})
}
}
fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
// A function literal is the subtype of itself, and not of any other function literal.
// However, our representation of a function literal includes any specialization that
// should be applied to the signature. Different specializations of the same function
// literal are only subtypes of each other if they result in subtype signatures.
self.normalized(db) == other.normalized(db)
|| (self.body_scope(db) == other.body_scope(db)
&& self
.into_callable_type(db)
.is_subtype_of(db, other.into_callable_type(db)))
}
fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
// A function literal is assignable to itself, and not to any other function literal.
// However, our representation of a function literal includes any specialization that
// should be applied to the signature. Different specializations of the same function
// literal are only assignable to each other if they result in assignable signatures.
self.body_scope(db) == other.body_scope(db)
&& self
.into_callable_type(db)
.is_assignable_to(db, other.into_callable_type(db))
}
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.normalized(db) == other.normalized(db)
|| (self.body_scope(db) == other.body_scope(db)
&& self
.into_callable_type(db)
.is_equivalent_to(db, other.into_callable_type(db)))
}
fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.body_scope(db) == other.body_scope(db)
&& self
.into_callable_type(db)
.is_gradual_equivalent_to(db, other.into_callable_type(db))
}
fn normalized(self, db: &'db dyn Db) -> Self {
let context = self
.inherited_generic_context(db)
.map(|ctx| ctx.normalized(db));
let mappings: Box<_> = self
.type_mappings(db)
.iter()
.map(|mapping| mapping.normalized(db))
.collect();
Self::new(
db,
self.name(db),
self.known(db),
self.body_scope(db),
self.decorators(db),
self.dataclass_transformer_params(db),
context,
mappings,
)
}
/// Returns a tuple of two spans. The first is
/// the span for the identifier of the function
/// definition for `self`. The second is
/// the span for the parameter in the function
/// definition for `self`.
///
/// If there are no meaningful spans, then this
/// returns `None`. For example, when this type
/// isn't callable.
///
/// When `parameter_index` is `None`, then the
/// second span returned covers the entire parameter
/// list.
///
/// # Performance
///
/// Note that this may introduce cross-module
/// dependencies. This can have an impact on
/// the effectiveness of incremental caching
/// and should therefore be used judiciously.
///
/// An example of a good use case is to improve
/// a diagnostic.
fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
let function_scope = self.body_scope(db);
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let func_def = node.as_function()?;
let range = parameter_index
.and_then(|parameter_index| {
func_def
.parameters
.iter()
.nth(parameter_index)
.map(|param| param.range())
})
.unwrap_or(func_def.parameters.range);
let name_span = span.clone().with_range(func_def.name.range);
let parameter_span = span.with_range(range);
Some((name_span, parameter_span))
}
/// Returns a collection of useful spans for a
/// function signature. These are useful for
/// creating annotations on diagnostics.
///
/// # Performance
///
/// Note that this may introduce cross-module
/// dependencies. This can have an impact on
/// the effectiveness of incremental caching
/// and should therefore be used judiciously.
///
/// An example of a good use case is to improve
/// a diagnostic.
fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
let function_scope = self.body_scope(db);
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let func_def = node.as_function()?;
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
let mut signature = func_def.name.range.cover(func_def.parameters.range);
if let Some(return_type_range) = return_type_range {
signature = signature.cover(return_type_range);
}
Some(FunctionSpans {
signature: span.clone().with_range(signature),
name: span.clone().with_range(func_def.name.range),
parameters: span.clone().with_range(func_def.parameters.range),
return_type: return_type_range.map(|range| span.clone().with_range(range)),
})
}
}
/// A collection of useful spans for annotating functions.
///
/// This can be retrieved via `FunctionType::spans` or
/// `Type::function_spans`.
struct FunctionSpans {
/// The span of the entire function "signature." This includes
/// the name, parameter list and return type (if present).
signature: Span,
/// The span of the function name. i.e., `foo` in `def foo(): ...`.
name: Span,
/// The span of the parameter list, including the opening and
/// closing parentheses.
#[expect(dead_code)]
parameters: Span,
/// The span of the annotated return type, if present.
return_type: Option<Span>,
}
fn signature_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &FunctionSignature<'db>,
_count: u32,
_function: FunctionType<'db>,
) -> salsa::CycleRecoveryAction<FunctionSignature<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn signature_cycle_initial<'db>(
db: &'db dyn Db,
_function: FunctionType<'db>,
) -> FunctionSignature<'db> {
FunctionSignature::bottom(db)
}
/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might
/// have special behavior.
#[derive(
Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr,
)]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum KnownFunction {
/// `builtins.isinstance`
#[strum(serialize = "isinstance")]
IsInstance,
/// `builtins.issubclass`
#[strum(serialize = "issubclass")]
IsSubclass,
/// `builtins.hasattr`
#[strum(serialize = "hasattr")]
HasAttr,
/// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type`
RevealType,
/// `builtins.len`
Len,
/// `builtins.repr`
Repr,
/// `typing(_extensions).final`
Final,
/// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check)
NoTypeCheck,
/// `typing(_extensions).assert_type`
AssertType,
/// `typing(_extensions).assert_never`
AssertNever,
/// `typing(_extensions).cast`
Cast,
/// `typing(_extensions).overload`
Overload,
/// `typing(_extensions).override`
Override,
/// `typing(_extensions).is_protocol`
IsProtocol,
/// `typing(_extensions).get_protocol_members`
GetProtocolMembers,
/// `typing(_extensions).runtime_checkable`
RuntimeCheckable,
/// `typing(_extensions).dataclass_transform`
DataclassTransform,
/// `abc.abstractmethod`
#[strum(serialize = "abstractmethod")]
AbstractMethod,
/// `dataclasses.dataclass`
Dataclass,
/// `inspect.getattr_static`
GetattrStatic,
/// `ty_extensions.static_assert`
StaticAssert,
/// `ty_extensions.is_equivalent_to`
IsEquivalentTo,
/// `ty_extensions.is_subtype_of`
IsSubtypeOf,
/// `ty_extensions.is_assignable_to`
IsAssignableTo,
/// `ty_extensions.is_disjoint_from`
IsDisjointFrom,
/// `ty_extensions.is_gradual_equivalent_to`
IsGradualEquivalentTo,
/// `ty_extensions.is_fully_static`
IsFullyStatic,
/// `ty_extensions.is_singleton`
IsSingleton,
/// `ty_extensions.is_single_valued`
IsSingleValued,
/// `ty_extensions.generic_context`
GenericContext,
/// `ty_extensions.dunder_all_names`
DunderAllNames,
/// `ty_extensions.all_members`
AllMembers,
}
impl KnownFunction {
pub fn into_classinfo_constraint_function(self) -> Option<ClassInfoConstraintFunction> {
match self {
Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance),
Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass),
_ => None,
}
}
fn try_from_definition_and_name<'db>(
db: &'db dyn Db,
definition: Definition<'db>,
name: &str,
) -> Option<Self> {
let candidate = Self::from_str(name).ok()?;
candidate
.check_module(file_to_module(db, definition.file(db))?.known()?)
.then_some(candidate)
}
/// Return `true` if `self` is defined in `module` at runtime.
const fn check_module(self, module: KnownModule) -> bool {
match self {
Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => {
module.is_builtins()
}
Self::AssertType
| Self::AssertNever
| Self::Cast
| Self::Overload
| Self::Override
| Self::RevealType
| Self::Final
| Self::IsProtocol
| Self::GetProtocolMembers
| Self::RuntimeCheckable
| Self::DataclassTransform
| Self::NoTypeCheck => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
Self::AbstractMethod => {
matches!(module, KnownModule::Abc)
}
Self::Dataclass => {
matches!(module, KnownModule::Dataclasses)
}
Self::GetattrStatic => module.is_inspect(),
Self::IsAssignableTo
| Self::IsDisjointFrom
| Self::IsEquivalentTo
| Self::IsGradualEquivalentTo
| Self::IsFullyStatic
| Self::IsSingleValued
| Self::IsSingleton
| Self::IsSubtypeOf
| Self::GenericContext
| Self::DunderAllNames
| Self::StaticAssert
| Self::AllMembers => module.is_ty_extensions(),
}
}
}
/// This type represents bound method objects that are created when a method is accessed
/// on an instance of a class. For example, the expression `Path("a.txt").touch` creates
/// a bound method object that represents the `Path.touch` method which is bound to the
@ -9213,15 +8489,12 @@ static_assertions::assert_eq_size!(Type, [u8; 16]);
pub(crate) mod tests {
use super::*;
use crate::db::tests::{TestDbBuilder, setup_db};
use crate::symbol::{
global_symbol, known_module_symbol, typing_extensions_symbol, typing_symbol,
};
use crate::symbol::{global_symbol, typing_extensions_symbol, typing_symbol};
use ruff_db::files::system_path_to_file;
use ruff_db::parsed::parsed_module;
use ruff_db::system::DbWithWritableSystem as _;
use ruff_db::testing::assert_function_query_was_not_run;
use ruff_python_ast::PythonVersion;
use strum::IntoEnumIterator;
use test_case::test_case;
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
@ -9364,69 +8637,4 @@ pub(crate) mod tests {
.is_todo()
);
}
#[test]
fn known_function_roundtrip_from_str() {
let db = setup_db();
for function in KnownFunction::iter() {
let function_name: &'static str = function.into();
let module = match function {
KnownFunction::Len
| KnownFunction::Repr
| KnownFunction::IsInstance
| KnownFunction::HasAttr
| KnownFunction::IsSubclass => KnownModule::Builtins,
KnownFunction::AbstractMethod => KnownModule::Abc,
KnownFunction::Dataclass => KnownModule::Dataclasses,
KnownFunction::GetattrStatic => KnownModule::Inspect,
KnownFunction::Cast
| KnownFunction::Final
| KnownFunction::Overload
| KnownFunction::Override
| KnownFunction::RevealType
| KnownFunction::AssertType
| KnownFunction::AssertNever
| KnownFunction::IsProtocol
| KnownFunction::GetProtocolMembers
| KnownFunction::RuntimeCheckable
| KnownFunction::DataclassTransform
| KnownFunction::NoTypeCheck => KnownModule::TypingExtensions,
KnownFunction::IsSingleton
| KnownFunction::IsSubtypeOf
| KnownFunction::GenericContext
| KnownFunction::DunderAllNames
| KnownFunction::StaticAssert
| KnownFunction::IsFullyStatic
| KnownFunction::IsDisjointFrom
| KnownFunction::IsSingleValued
| KnownFunction::IsAssignableTo
| KnownFunction::IsEquivalentTo
| KnownFunction::IsGradualEquivalentTo
| KnownFunction::AllMembers => KnownModule::TyExtensions,
};
let function_definition = known_module_symbol(&db, module, function_name)
.symbol
.expect_type()
.expect_function_literal()
.definition(&db);
assert_eq!(
KnownFunction::try_from_definition_and_name(
&db,
function_definition,
function_name
),
Some(function),
"The strum `EnumString` implementation appears to be incorrect for `{function_name}`"
);
}
}
}

View file

@ -18,13 +18,13 @@ use crate::types::diagnostic::{
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
UNKNOWN_ARGUMENT,
};
use crate::types::function::{DataclassTransformerParams, FunctionDecorators, KnownFunction};
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
use crate::types::signatures::{Parameter, ParameterForm};
use crate::types::{
BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType,
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support,
todo_type,
BoundMethodType, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind,
PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType,
WrapperDescriptorKind, ide_support, todo_type,
};
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
use ruff_python_ast as ast;
@ -871,47 +871,47 @@ impl<'db> Bindings<'db> {
}
_ => {
let mut handle_dataclass_transformer_params =
|function_type: &FunctionType| {
if let Some(params) =
function_type.dataclass_transformer_params(db)
{
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
// If this function was called with a keyword argument like `order=False`, we extract
// the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults.
let mut dataclass_params = DataclassParams::from(params);
if let Some(Some(Type::BooleanLiteral(order))) = overload
.signature
.parameters()
.keyword_by_name("order")
.map(|(idx, _)| idx)
.and_then(|idx| overload.parameter_types().get(idx))
{
dataclass_params.set(DataclassParams::ORDER, *order);
}
overload.set_return_type(Type::DataclassDecorator(
dataclass_params,
));
}
};
// Ideally, either the implementation, or exactly one of the overloads
// of the function can have the dataclass_transform decorator applied.
// However, we do not yet enforce this, and in the case of multiple
// applications of the decorator, we will only consider the last one
// for the return value, since the prior ones will be over-written.
if let Some(overloaded) = function_type.to_overloaded(db) {
overloaded
.overloads
.iter()
.for_each(&mut handle_dataclass_transformer_params);
}
let return_type = function_type
.iter_overloads_and_implementation(db)
.filter_map(|function_overload| {
function_overload.dataclass_transformer_params(db).map(
|params| {
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
// If this function was called with a keyword argument like `order=False`, we extract
// the argument type and overwrite the corresponding flag in `dataclass_params` after
// constructing them from the `dataclass_transformer`-parameter defaults.
handle_dataclass_transformer_params(&function_type);
let mut dataclass_params =
DataclassParams::from(params);
if let Some(Some(Type::BooleanLiteral(order))) =
overload
.signature
.parameters()
.keyword_by_name("order")
.map(|(idx, _)| idx)
.and_then(|idx| {
overload.parameter_types().get(idx)
})
{
dataclass_params
.set(DataclassParams::ORDER, *order);
}
Type::DataclassDecorator(dataclass_params)
},
)
})
.last();
if let Some(return_type) = return_type {
overload.set_return_type(return_type);
}
}
},
@ -1261,47 +1261,49 @@ impl<'db> CallableBinding<'db> {
_ => None,
};
if let Some((kind, function)) = function_type_and_kind {
if let Some(overloaded_function) = function.to_overloaded(context.db()) {
if let Some(spans) = overloaded_function
.overloads
.first()
.and_then(|overload| overload.spans(context.db()))
{
let mut sub =
SubDiagnostic::new(Severity::Info, "First overload defined here");
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
let (overloads, implementation) =
function.overloads_and_implementation(context.db());
if let Some(spans) = overloads
.first()
.and_then(|overload| overload.spans(context.db()))
{
let mut sub =
SubDiagnostic::new(Severity::Info, "First overload defined here");
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
diag.info(format_args!(
"Possible overloads for {kind} `{}`:",
function.name(context.db())
));
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
diag.info(format_args!(
"Possible overloads for {kind} `{}`:",
function.name(context.db())
" {}",
overload.signature(context.db(), None).display(context.db())
));
}
if overloads.len() > MAXIMUM_OVERLOADS {
diag.info(format_args!(
"... omitted {remaining} overloads",
remaining = overloads.len() - MAXIMUM_OVERLOADS
));
}
let overloads = &function.signature(context.db()).overloads.overloads;
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
diag.info(format_args!(" {}", overload.display(context.db())));
}
if overloads.len() > MAXIMUM_OVERLOADS {
diag.info(format_args!(
"... omitted {remaining} overloads",
remaining = overloads.len() - MAXIMUM_OVERLOADS
));
}
if let Some(spans) = overloaded_function
.implementation
.and_then(|function| function.spans(context.db()))
{
let mut sub = SubDiagnostic::new(
Severity::Info,
"Overload implementation defined here",
);
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
if let Some(spans) =
implementation.and_then(|function| function.spans(context.db()))
{
let mut sub = SubDiagnostic::new(
Severity::Info,
"Overload implementation defined here",
);
sub.annotate(Annotation::primary(spans.signature));
diag.sub(sub);
}
}
if let Some(union_diag) = union_diag {
union_diag.add_union_context(context.db(), &mut diag);
}

View file

@ -2,17 +2,17 @@ use std::hash::BuildHasherDefault;
use std::sync::{LazyLock, Mutex};
use super::{
IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator,
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase,
infer_expression_type, infer_unpack_types,
IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType,
SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type,
infer_unpack_types,
};
use crate::semantic_index::DeclarationWithConstraint;
use crate::semantic_index::definition::Definition;
use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::{
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping,
TypeVarInstance,
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeVarInstance,
};
use crate::{
Db, FxOrderSet, KnownModule, Program,

View file

@ -11,13 +11,14 @@ use ruff_text_size::{Ranged, TextRange};
use super::{Type, TypeCheckDiagnostics, binding_type};
use crate::lint::LintSource;
use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::ScopeId;
use crate::types::function::FunctionDecorators;
use crate::{
Db,
lint::{LintId, LintMetadata},
suppression::suppressions,
};
use crate::{semantic_index::semantic_index, types::FunctionDecorators};
/// Context for inferring the types of a single file.
///

View file

@ -8,12 +8,13 @@ use super::{
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::suppression::FileSuppressionId;
use crate::types::LintDiagnosticGuard;
use crate::types::function::KnownFunction;
use crate::types::string_annotation::{
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
use crate::{Db, Module, ModuleName, Program, declare_lint};
use itertools::Itertools;
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};

View file

@ -7,6 +7,7 @@ use ruff_python_ast::str::{Quote, TripleQuotes};
use ruff_python_literal::escape::AsciiEscape;
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
use crate::types::function::{FunctionType, OverloadLiteral};
use crate::types::generics::{GenericContext, Specialization};
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::{
@ -112,34 +113,7 @@ impl Display for DisplayRepresentation<'_> {
},
Type::SpecialForm(special_form) => special_form.fmt(f),
Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f),
Type::FunctionLiteral(function) => {
let signature = function.signature(self.db);
// TODO: when generic function types are supported, we should add
// the generic type parameters to the signature, i.e.
// show `def foo[T](x: T) -> T`.
match signature.overloads.as_slice() {
[signature] => {
write!(
f,
// "def {name}{specialization}{signature}",
"def {name}{signature}",
name = function.name(self.db),
signature = signature.display(self.db)
)
}
signatures => {
// TODO: How to display overloads?
f.write_str("Overload[")?;
let mut join = f.join(", ");
for signature in signatures {
join.entry(&signature.display(self.db));
}
f.write_str("]")
}
}
}
Type::FunctionLiteral(function) => function.display(self.db).fmt(f),
Type::Callable(callable) => callable.display(self.db).fmt(f),
Type::BoundMethod(bound_method) => {
let function = bound_method.function(self.db);
@ -241,6 +215,71 @@ impl Display for DisplayRepresentation<'_> {
}
}
impl<'db> OverloadLiteral<'db> {
// Not currently used, but useful for debugging.
#[expect(dead_code)]
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayOverloadLiteral<'db> {
DisplayOverloadLiteral { literal: self, db }
}
}
pub(crate) struct DisplayOverloadLiteral<'db> {
literal: OverloadLiteral<'db>,
db: &'db dyn Db,
}
impl Display for DisplayOverloadLiteral<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let signature = self.literal.signature(self.db, None);
write!(
f,
"def {name}{signature}",
name = self.literal.name(self.db),
signature = signature.display(self.db)
)
}
}
impl<'db> FunctionType<'db> {
pub(crate) fn display(self, db: &'db dyn Db) -> DisplayFunctionType<'db> {
DisplayFunctionType { ty: self, db }
}
}
pub(crate) struct DisplayFunctionType<'db> {
ty: FunctionType<'db>,
db: &'db dyn Db,
}
impl Display for DisplayFunctionType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let signature = self.ty.signature(self.db);
// TODO: We should consider adding the type parameters to the signature of a generic
// function, i.e. `def foo[T](x: T) -> T`.
match signature.overloads.as_slice() {
[signature] => {
write!(
f,
"def {name}{signature}",
name = self.ty.name(self.db),
signature = signature.display(self.db)
)
}
signatures => {
// TODO: How to display overloads?
f.write_str("Overload[")?;
let mut join = f.join(", ");
for signature in signatures {
join.entry(&signature.display(self.db));
}
f.write_str("]")
}
}
}
}
impl<'db> GenericAlias<'db> {
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> {
DisplayGenericAlias {

View file

@ -0,0 +1,996 @@
//! Contains representations of function literals. There are several complicating factors:
//!
//! - Functions can be generic, and can have specializations applied to them. These are not the
//! same thing! For instance, a method of a generic class might not itself be generic, but it can
//! still have the class's specialization applied to it.
//!
//! - Functions can be overloaded, and each overload can be independently generic or not, with
//! different sets of typevars for different generic overloads. In some cases we need to consider
//! each overload separately; in others we need to consider all of the overloads (and any
//! implementation) as a single collective entity.
//!
//! - Certain “known” functions need special treatment — for instance, inferring a special return
//! type, or raising custom diagnostics.
//!
//! - TODO: Some functions don't correspond to a function definition in the AST, and are instead
//! synthesized as we mimic the behavior of the Python interpreter. Even though they are
//! synthesized, and are “implemented” as Rust code, they are still functions from the POV of the
//! rest of the type system.
//!
//! Given these constraints, we have the following representation: a function is a list of one or
//! more overloads, with zero or more specializations (more specifically, “type mappings”) applied
//! to it. [`FunctionType`] is the outermost type, which is what [`Type::FunctionLiteral`] wraps.
//! It contains the list of type mappings to apply. It wraps a [`FunctionLiteral`], which collects
//! together all of the overloads (and implementation) of an overloaded function. An
//! [`OverloadLiteral`] represents an individual function definition in the AST — that is, each
//! overload (and implementation) of an overloaded function, or the single definition of a
//! non-overloaded function.
//!
//! Technically, each `FunctionLiteral` wraps a particular overload and all _previous_ overloads.
//! So it's only true that it wraps _all_ overloads if you are looking at the last definition. For
//! instance, in
//!
//! ```py
//! @overload
//! def f(x: int) -> None: ...
//! # <-- 1
//!
//! @overload
//! def f(x: str) -> None: ...
//! # <-- 2
//!
//! def f(x): pass
//! # <-- 3
//! ```
//!
//! resolving `f` at each of the three numbered positions will give you a `FunctionType`, which
//! wraps a `FunctionLiteral`, which contain `OverloadLiteral`s only for the definitions that
//! appear before that position. We rely on the fact that later definitions shadow earlier ones, so
//! the public type of `f` is resolved at position 3, correctly giving you all of the overloads
//! (and the implementation).
use std::str::FromStr;
use bitflags::bitflags;
use ruff_db::diagnostic::Span;
use ruff_db::files::{File, FileRange};
use ruff_python_ast as ast;
use ruff_text_size::Ranged;
use crate::module_resolver::{KnownModule, file_to_module};
use crate::semantic_index::ast_ids::HasScopedUseId;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::ScopeId;
use crate::symbol::{Boundness, Symbol, symbol_from_bindings};
use crate::types::generics::GenericContext;
use crate::types::narrow::ClassInfoConstraintFunction;
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::{BoundMethodType, CallableType, Type, TypeMapping, TypeVarInstance};
use crate::{Db, FxOrderSet};
/// A collection of useful spans for annotating functions.
///
/// This can be retrieved via `FunctionType::spans` or
/// `Type::function_spans`.
pub(crate) struct FunctionSpans {
/// The span of the entire function "signature." This includes
/// the name, parameter list and return type (if present).
pub(crate) signature: Span,
/// The span of the function name. i.e., `foo` in `def foo(): ...`.
pub(crate) name: Span,
/// The span of the parameter list, including the opening and
/// closing parentheses.
#[expect(dead_code)]
pub(crate) parameters: Span,
/// The span of the annotated return type, if present.
pub(crate) return_type: Option<Span>,
}
bitflags! {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)]
pub struct FunctionDecorators: u8 {
/// `@classmethod`
const CLASSMETHOD = 1 << 0;
/// `@typing.no_type_check`
const NO_TYPE_CHECK = 1 << 1;
/// `@typing.overload`
const OVERLOAD = 1 << 2;
/// `@abc.abstractmethod`
const ABSTRACT_METHOD = 1 << 3;
/// `@typing.final`
const FINAL = 1 << 4;
/// `@typing.override`
const OVERRIDE = 1 << 6;
}
}
bitflags! {
/// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the
/// arguments that were passed in. For the precise meaning of the fields, see [1].
///
/// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub struct DataclassTransformerParams: u8 {
const EQ_DEFAULT = 1 << 0;
const ORDER_DEFAULT = 1 << 1;
const KW_ONLY_DEFAULT = 1 << 2;
const FROZEN_DEFAULT = 1 << 3;
}
}
impl Default for DataclassTransformerParams {
fn default() -> Self {
Self::EQ_DEFAULT
}
}
/// Representation of a function definition in the AST: either a non-generic function, or a generic
/// function that has not been specialized.
///
/// If a function has multiple overloads, each overload is represented by a separate function
/// definition in the AST, and is therefore a separate `OverloadLiteral` instance.
///
/// # Ordering
/// Ordering is based on the function's id assigned by salsa and not on the function literal's
/// values. The id may change between runs, or when the function literal was garbage collected and
/// recreated.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct OverloadLiteral<'db> {
/// Name of the function at definition.
#[returns(ref)]
pub name: ast::name::Name,
/// Is this a function that we special-case somehow? If so, which one?
pub(crate) known: Option<KnownFunction>,
/// The scope that's created by the function, in which the function body is evaluated.
pub(crate) body_scope: ScopeId<'db>,
/// A set of special decorators that were applied to this function
pub(crate) decorators: FunctionDecorators,
/// The arguments to `dataclass_transformer`, if this function was annotated
/// with `@dataclass_transformer(...)`.
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>,
}
#[salsa::tracked]
impl<'db> OverloadLiteral<'db> {
fn with_dataclass_transformer_params(
self,
db: &'db dyn Db,
params: DataclassTransformerParams,
) -> Self {
Self::new(
db,
self.name(db).clone(),
self.known(db),
self.body_scope(db),
self.decorators(db),
Some(params),
)
}
fn file(self, db: &'db dyn Db) -> File {
// NOTE: Do not use `self.definition(db).file(db)` here, as that could create a
// cross-module dependency on the full AST.
self.body_scope(db).file(db)
}
pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.decorators(db).contains(decorator)
}
pub(crate) fn is_overload(self, db: &dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::OVERLOAD)
}
fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef {
debug_assert_eq!(
file,
self.file(db),
"OverloadLiteral::node() must be called with the same file as the one where \
the function is defined."
);
self.body_scope(db).node(db).expect_function()
}
/// Returns the [`FileRange`] of the function's name.
pub(crate) fn focus_range(self, db: &dyn Db) -> FileRange {
FileRange::new(
self.file(db),
self.body_scope(db).node(db).expect_function().name.range,
)
}
/// Returns the [`Definition`] of this function.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let body_scope = self.body_scope(db);
let index = semantic_index(db, body_scope.file(db));
index.expect_single_definition(body_scope.node(db).expect_function())
}
/// Returns the overload immediately before this one in the AST. Returns `None` if there is no
/// previous overload.
fn previous_overload(self, db: &'db dyn Db) -> Option<FunctionLiteral<'db>> {
// The semantic model records a use for each function on the name node. This is used
// here to get the previous function definition with the same name.
let scope = self.definition(db).scope(db);
let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db));
let use_id = self
.body_scope(db)
.node(db)
.expect_function()
.name
.scoped_use_id(db, scope);
let Symbol::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) =
symbol_from_bindings(db, use_def.bindings_at_use(use_id))
else {
return None;
};
let previous_literal = previous_type.literal(db);
let previous_overload = previous_literal.last_definition(db);
if !previous_overload.is_overload(db) {
return None;
}
Some(previous_literal)
}
/// Typed internally-visible signature for this function.
///
/// This represents the annotations on the function itself, unmodified by decorators and
/// overloads.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
pub(crate) fn signature(
self,
db: &'db dyn Db,
inherited_generic_context: Option<GenericContext<'db>>,
) -> Signature<'db> {
let scope = self.body_scope(db);
let function_stmt_node = scope.node(db).expect_function();
let definition = self.definition(db);
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, type_params)
});
Signature::from_function(
db,
generic_context,
inherited_generic_context,
definition,
function_stmt_node,
)
}
fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
let function_scope = self.body_scope(db);
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let func_def = node.as_function()?;
let range = parameter_index
.and_then(|parameter_index| {
func_def
.parameters
.iter()
.nth(parameter_index)
.map(|param| param.range())
})
.unwrap_or(func_def.parameters.range);
let name_span = span.clone().with_range(func_def.name.range);
let parameter_span = span.with_range(range);
Some((name_span, parameter_span))
}
pub(crate) fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
let function_scope = self.body_scope(db);
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let func_def = node.as_function()?;
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
let mut signature = func_def.name.range.cover(func_def.parameters.range);
if let Some(return_type_range) = return_type_range {
signature = signature.cover(return_type_range);
}
Some(FunctionSpans {
signature: span.clone().with_range(signature),
name: span.clone().with_range(func_def.name.range),
parameters: span.clone().with_range(func_def.parameters.range),
return_type: return_type_range.map(|range| span.clone().with_range(range)),
})
}
}
/// Representation of a function definition in the AST, along with any previous overloads of the
/// function. Each overload can be separately generic or not, and each generic overload uses
/// distinct typevars.
///
/// # Ordering
/// Ordering is based on the function's id assigned by salsa and not on the function literal's
/// values. The id may change between runs, or when the function literal was garbage collected and
/// recreated.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct FunctionLiteral<'db> {
pub(crate) last_definition: OverloadLiteral<'db>,
/// The inherited generic context, if this function is a constructor method (`__new__` or
/// `__init__`) being used to infer the specialization of its generic class. If any of the
/// method's overloads are themselves generic, this is in addition to those per-overload
/// generic contexts (which are created lazily in [`OverloadLiteral::signature`]).
///
/// If the function is not a constructor method, this field will always be `None`.
///
/// If the function is a constructor method, we will end up creating two `FunctionLiteral`
/// instances for it. The first is created in [`TypeInferenceBuilder`][infer] when we encounter
/// the function definition during type inference. At this point, we don't yet know if the
/// function is a constructor method, so we create a `FunctionLiteral` with `None` for this
/// field.
///
/// If at some point we encounter a call expression, which invokes the containing class's
/// constructor, as will create a _new_ `FunctionLiteral` instance for the function, with this
/// field [updated][] to contain the containing class's generic context.
///
/// [infer]: crate::types::infer::TypeInferenceBuilder::infer_function_definition
/// [updated]: crate::types::class::ClassLiteral::own_class_member
inherited_generic_context: Option<GenericContext<'db>>,
}
#[salsa::tracked]
impl<'db> FunctionLiteral<'db> {
fn with_inherited_generic_context(
self,
db: &'db dyn Db,
inherited_generic_context: GenericContext<'db>,
) -> Self {
// A function cannot inherit more than one generic context from its containing class.
debug_assert!(self.inherited_generic_context(db).is_none());
Self::new(
db,
self.last_definition(db),
Some(inherited_generic_context),
)
}
fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
// All of the overloads of a function literal should have the same name.
self.last_definition(db).name(db)
}
fn known(self, db: &'db dyn Db) -> Option<KnownFunction> {
// Whether a function is known is based on its name (and its containing module's name), so
// all overloads should be known (or not) equivalently.
self.last_definition(db).known(db)
}
fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.iter_overloads_and_implementation(db)
.any(|overload| overload.decorators(db).contains(decorator))
}
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.last_definition(db).definition(db)
}
fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
self.last_definition(db).parameter_span(db, parameter_index)
}
fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
self.last_definition(db).spans(db)
}
#[salsa::tracked(returns(ref))]
fn overloads_and_implementation(
self,
db: &'db dyn Db,
) -> (Box<[OverloadLiteral<'db>]>, Option<OverloadLiteral<'db>>) {
let self_overload = self.last_definition(db);
let mut current = self_overload;
let mut overloads = vec![];
while let Some(previous) = current.previous_overload(db) {
let overload = previous.last_definition(db);
overloads.push(overload);
current = overload;
}
// Overloads are inserted in reverse order, from bottom to top.
overloads.reverse();
let implementation = if self_overload.is_overload(db) {
overloads.push(self_overload);
None
} else {
Some(self_overload)
};
(overloads.into_boxed_slice(), implementation)
}
fn iter_overloads_and_implementation(
self,
db: &'db dyn Db,
) -> impl Iterator<Item = OverloadLiteral<'db>> + 'db {
let (implementation, overloads) = self.overloads_and_implementation(db);
overloads.iter().chain(implementation).copied()
}
/// Typed externally-visible signature for this function.
///
/// This is the signature as seen by external callers, possibly modified by decorators and/or
/// overloaded.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
fn signature<'a>(
self,
db: &'db dyn Db,
type_mappings: &'a [TypeMapping<'a, 'db>],
) -> CallableSignature<'db>
where
'db: 'a,
{
// We only include an implementation (i.e. a definition not decorated with `@overload`) if
// it's the only definition.
let inherited_generic_context = self.inherited_generic_context(db);
let (overloads, implementation) = self.overloads_and_implementation(db);
if let Some(implementation) = implementation {
if overloads.is_empty() {
return CallableSignature::single(type_mappings.iter().fold(
implementation.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
));
}
}
CallableSignature::from_overloads(overloads.iter().map(|overload| {
type_mappings.iter().fold(
overload.signature(db, inherited_generic_context),
|ty, mapping| ty.apply_type_mapping(db, mapping),
)
}))
}
fn normalized(self, db: &'db dyn Db) -> Self {
let context = self
.inherited_generic_context(db)
.map(|ctx| ctx.normalized(db));
Self::new(db, self.last_definition(db), context)
}
}
/// Represents a function type, which might be a non-generic function, or a specialization of a
/// generic function.
#[salsa::interned(debug)]
#[derive(PartialOrd, Ord)]
pub struct FunctionType<'db> {
pub(crate) literal: FunctionLiteral<'db>,
/// Type mappings that should be applied to the function's parameter and return types. This
/// might include specializations of enclosing generic contexts (e.g. for non-generic methods
/// of a specialized generic class).
#[returns(deref)]
type_mappings: Box<[TypeMapping<'db, 'db>]>,
}
#[salsa::tracked]
impl<'db> FunctionType<'db> {
pub(crate) fn with_inherited_generic_context(
self,
db: &'db dyn Db,
inherited_generic_context: GenericContext<'db>,
) -> Self {
let literal = self
.literal(db)
.with_inherited_generic_context(db, inherited_generic_context);
Self::new(db, literal, self.type_mappings(db))
}
pub(crate) fn with_type_mapping<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
) -> Self {
let type_mappings: Box<[_]> = self
.type_mappings(db)
.iter()
.cloned()
.chain(std::iter::once(type_mapping.to_owned()))
.collect();
Self::new(db, self.literal(db), type_mappings)
}
pub(crate) fn with_dataclass_transformer_params(
self,
db: &'db dyn Db,
params: DataclassTransformerParams,
) -> Self {
// A decorator only applies to the specific overload that it is attached to, not to all
// previous overloads.
let literal = self.literal(db);
let last_definition = literal
.last_definition(db)
.with_dataclass_transformer_params(db, params);
let literal =
FunctionLiteral::new(db, last_definition, literal.inherited_generic_context(db));
Self::new(db, literal, self.type_mappings(db))
}
/// Returns the [`File`] in which this function is defined.
pub(crate) fn file(self, db: &'db dyn Db) -> File {
self.literal(db).last_definition(db).file(db)
}
/// Returns the AST node for this function.
pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef {
self.literal(db).last_definition(db).node(db, file)
}
pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name {
self.literal(db).name(db)
}
pub(crate) fn known(self, db: &'db dyn Db) -> Option<KnownFunction> {
self.literal(db).known(db)
}
pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool {
self.known(db) == Some(known_function)
}
/// Returns if any of the overloads of this function have a particular decorator.
///
/// Some decorators are expected to appear on every overload; others are expected to appear
/// only the implementation or first overload. This method does not check either of those
/// conditions.
pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool {
self.literal(db).has_known_decorator(db, decorator)
}
/// Returns the [`Definition`] of the implementation or first overload of this function.
///
/// ## Warning
///
/// This uses the semantic index to find the definition of the function. This means that if the
/// calling query is not in the same file as this function is defined in, then this will create
/// a cross-module dependency directly on the full AST which will lead to cache
/// over-invalidation.
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.literal(db).definition(db)
}
/// Returns a tuple of two spans. The first is
/// the span for the identifier of the function
/// definition for `self`. The second is
/// the span for the parameter in the function
/// definition for `self`.
///
/// If there are no meaningful spans, then this
/// returns `None`. For example, when this type
/// isn't callable.
///
/// When `parameter_index` is `None`, then the
/// second span returned covers the entire parameter
/// list.
///
/// # Performance
///
/// Note that this may introduce cross-module
/// dependencies. This can have an impact on
/// the effectiveness of incremental caching
/// and should therefore be used judiciously.
///
/// An example of a good use case is to improve
/// a diagnostic.
pub(crate) fn parameter_span(
self,
db: &'db dyn Db,
parameter_index: Option<usize>,
) -> Option<(Span, Span)> {
self.literal(db).parameter_span(db, parameter_index)
}
/// Returns a collection of useful spans for a
/// function signature. These are useful for
/// creating annotations on diagnostics.
///
/// # Performance
///
/// Note that this may introduce cross-module
/// dependencies. This can have an impact on
/// the effectiveness of incremental caching
/// and should therefore be used judiciously.
///
/// An example of a good use case is to improve
/// a diagnostic.
pub(crate) fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
self.literal(db).spans(db)
}
/// Returns all of the overload signatures and the implementation definition, if any, of this
/// function. The overload signatures will be in source order.
pub(crate) fn overloads_and_implementation(
self,
db: &'db dyn Db,
) -> &'db (Box<[OverloadLiteral<'db>]>, Option<OverloadLiteral<'db>>) {
self.literal(db).overloads_and_implementation(db)
}
/// Returns an iterator of all of the definitions of this function, including both overload
/// signatures and any implementation, all in source order.
pub(crate) fn iter_overloads_and_implementation(
self,
db: &'db dyn Db,
) -> impl Iterator<Item = OverloadLiteral<'db>> + 'db {
self.literal(db).iter_overloads_and_implementation(db)
}
/// Typed externally-visible signature for this function.
///
/// This is the signature as seen by external callers, possibly modified by decorators and/or
/// overloaded.
///
/// ## Why is this a salsa query?
///
/// This is a salsa query to short-circuit the invalidation
/// when the function's AST node changes.
///
/// Were this not a salsa query, then the calling query
/// would depend on the function's AST and rerun for every change in that file.
#[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)]
pub(crate) fn signature(self, db: &'db dyn Db) -> CallableSignature<'db> {
self.literal(db).signature(db, self.type_mappings(db))
}
/// Convert the `FunctionType` into a [`Type::Callable`].
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
Type::Callable(CallableType::new(db, self.signature(db), false))
}
/// Convert the `FunctionType` into a [`Type::BoundMethod`].
pub(crate) fn into_bound_method_type(
self,
db: &'db dyn Db,
self_instance: Type<'db>,
) -> Type<'db> {
Type::BoundMethod(BoundMethodType::new(db, self, self_instance))
}
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
// A function type is the subtype of itself, and not of any other function type. However,
// our representation of a function type includes any specialization that should be applied
// to the signature. Different specializations of the same function type are only subtypes
// of each other if they result in subtype signatures.
if self.normalized(db) == other.normalized(db) {
return true;
}
if self.literal(db) != other.literal(db) {
return false;
}
let self_signature = self.signature(db);
let other_signature = other.signature(db);
if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) {
return false;
}
self_signature.is_subtype_of(db, other_signature)
}
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
// A function type is assignable to itself, and not to any other function type. However,
// our representation of a function type includes any specialization that should be applied
// to the signature. Different specializations of the same function type are only
// assignable to each other if they result in assignable signatures.
self.literal(db) == other.literal(db)
&& self.signature(db).is_assignable_to(db, other.signature(db))
}
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
if self.normalized(db) == other.normalized(db) {
return true;
}
if self.literal(db) != other.literal(db) {
return false;
}
let self_signature = self.signature(db);
let other_signature = other.signature(db);
if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) {
return false;
}
self_signature.is_equivalent_to(db, other_signature)
}
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
self.literal(db) == other.literal(db)
&& self
.signature(db)
.is_gradual_equivalent_to(db, other.signature(db))
}
pub(crate) fn find_legacy_typevars(
self,
db: &'db dyn Db,
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
) {
let signatures = self.signature(db);
for signature in &signatures.overloads {
signature.find_legacy_typevars(db, typevars);
}
}
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
let mappings: Box<_> = self
.type_mappings(db)
.iter()
.map(|mapping| mapping.normalized(db))
.collect();
Self::new(db, self.literal(db).normalized(db), mappings)
}
}
fn signature_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &CallableSignature<'db>,
_count: u32,
_function: FunctionType<'db>,
) -> salsa::CycleRecoveryAction<CallableSignature<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn signature_cycle_initial<'db>(
db: &'db dyn Db,
_function: FunctionType<'db>,
) -> CallableSignature<'db> {
CallableSignature::single(Signature::bottom(db))
}
/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might
/// have special behavior.
#[derive(
Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr,
)]
#[strum(serialize_all = "snake_case")]
#[cfg_attr(test, derive(strum_macros::EnumIter))]
pub enum KnownFunction {
/// `builtins.isinstance`
#[strum(serialize = "isinstance")]
IsInstance,
/// `builtins.issubclass`
#[strum(serialize = "issubclass")]
IsSubclass,
/// `builtins.hasattr`
#[strum(serialize = "hasattr")]
HasAttr,
/// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type`
RevealType,
/// `builtins.len`
Len,
/// `builtins.repr`
Repr,
/// `typing(_extensions).final`
Final,
/// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check)
NoTypeCheck,
/// `typing(_extensions).assert_type`
AssertType,
/// `typing(_extensions).assert_never`
AssertNever,
/// `typing(_extensions).cast`
Cast,
/// `typing(_extensions).overload`
Overload,
/// `typing(_extensions).override`
Override,
/// `typing(_extensions).is_protocol`
IsProtocol,
/// `typing(_extensions).get_protocol_members`
GetProtocolMembers,
/// `typing(_extensions).runtime_checkable`
RuntimeCheckable,
/// `typing(_extensions).dataclass_transform`
DataclassTransform,
/// `abc.abstractmethod`
#[strum(serialize = "abstractmethod")]
AbstractMethod,
/// `dataclasses.dataclass`
Dataclass,
/// `inspect.getattr_static`
GetattrStatic,
/// `ty_extensions.static_assert`
StaticAssert,
/// `ty_extensions.is_equivalent_to`
IsEquivalentTo,
/// `ty_extensions.is_subtype_of`
IsSubtypeOf,
/// `ty_extensions.is_assignable_to`
IsAssignableTo,
/// `ty_extensions.is_disjoint_from`
IsDisjointFrom,
/// `ty_extensions.is_gradual_equivalent_to`
IsGradualEquivalentTo,
/// `ty_extensions.is_fully_static`
IsFullyStatic,
/// `ty_extensions.is_singleton`
IsSingleton,
/// `ty_extensions.is_single_valued`
IsSingleValued,
/// `ty_extensions.generic_context`
GenericContext,
/// `ty_extensions.dunder_all_names`
DunderAllNames,
/// `ty_extensions.all_members`
AllMembers,
}
impl KnownFunction {
pub fn into_classinfo_constraint_function(self) -> Option<ClassInfoConstraintFunction> {
match self {
Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance),
Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass),
_ => None,
}
}
pub(crate) fn try_from_definition_and_name<'db>(
db: &'db dyn Db,
definition: Definition<'db>,
name: &str,
) -> Option<Self> {
let candidate = Self::from_str(name).ok()?;
candidate
.check_module(file_to_module(db, definition.file(db))?.known()?)
.then_some(candidate)
}
/// Return `true` if `self` is defined in `module` at runtime.
const fn check_module(self, module: KnownModule) -> bool {
match self {
Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => {
module.is_builtins()
}
Self::AssertType
| Self::AssertNever
| Self::Cast
| Self::Overload
| Self::Override
| Self::RevealType
| Self::Final
| Self::IsProtocol
| Self::GetProtocolMembers
| Self::RuntimeCheckable
| Self::DataclassTransform
| Self::NoTypeCheck => {
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
}
Self::AbstractMethod => {
matches!(module, KnownModule::Abc)
}
Self::Dataclass => {
matches!(module, KnownModule::Dataclasses)
}
Self::GetattrStatic => module.is_inspect(),
Self::IsAssignableTo
| Self::IsDisjointFrom
| Self::IsEquivalentTo
| Self::IsGradualEquivalentTo
| Self::IsFullyStatic
| Self::IsSingleValued
| Self::IsSingleton
| Self::IsSubtypeOf
| Self::GenericContext
| Self::DunderAllNames
| Self::StaticAssert
| Self::AllMembers => module.is_ty_extensions(),
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use strum::IntoEnumIterator;
use super::*;
use crate::db::tests::setup_db;
use crate::symbol::known_module_symbol;
#[test]
fn known_function_roundtrip_from_str() {
let db = setup_db();
for function in KnownFunction::iter() {
let function_name: &'static str = function.into();
let module = match function {
KnownFunction::Len
| KnownFunction::Repr
| KnownFunction::IsInstance
| KnownFunction::HasAttr
| KnownFunction::IsSubclass => KnownModule::Builtins,
KnownFunction::AbstractMethod => KnownModule::Abc,
KnownFunction::Dataclass => KnownModule::Dataclasses,
KnownFunction::GetattrStatic => KnownModule::Inspect,
KnownFunction::Cast
| KnownFunction::Final
| KnownFunction::Overload
| KnownFunction::Override
| KnownFunction::RevealType
| KnownFunction::AssertType
| KnownFunction::AssertNever
| KnownFunction::IsProtocol
| KnownFunction::GetProtocolMembers
| KnownFunction::RuntimeCheckable
| KnownFunction::DataclassTransform
| KnownFunction::NoTypeCheck => KnownModule::TypingExtensions,
KnownFunction::IsSingleton
| KnownFunction::IsSubtypeOf
| KnownFunction::GenericContext
| KnownFunction::DunderAllNames
| KnownFunction::StaticAssert
| KnownFunction::IsFullyStatic
| KnownFunction::IsDisjointFrom
| KnownFunction::IsSingleValued
| KnownFunction::IsAssignableTo
| KnownFunction::IsEquivalentTo
| KnownFunction::IsGradualEquivalentTo
| KnownFunction::AllMembers => KnownModule::TyExtensions,
};
let function_definition = known_module_symbol(&db, module, function_name)
.symbol
.expect_type()
.expect_function_literal()
.definition(&db);
assert_eq!(
KnownFunction::try_from_definition_and_name(
&db,
function_definition,
function_name
),
Some(function),
"The strum `EnumString` implementation appears to be incorrect for `{function_name}`"
);
}
}
}

View file

@ -81,19 +81,21 @@ use crate::types::diagnostic::{
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
report_invalid_return_type, report_possibly_unbound_attribute,
};
use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
};
use crate::types::generics::GenericContext;
use crate::types::mro::MroErrorKind;
use crate::types::signatures::{CallableSignature, Signature};
use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams,
DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder,
IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy,
MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType,
StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type,
todo_type,
DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass,
KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol,
SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
};
use crate::unpack::{Unpack, UnpackPosition};
use crate::util::subscript::{PyIndex, PySlice};
@ -1131,12 +1133,13 @@ impl<'db> TypeInferenceBuilder<'db> {
}
for function in self.called_functions.union(&public_functions) {
let Some(overloaded) = function.to_overloaded(self.db()) else {
let (overloads, implementation) = function.overloads_and_implementation(self.db());
if overloads.is_empty() {
continue;
};
}
// Check that the overloaded function has at least two overloads
if let [single_overload] = overloaded.overloads.as_slice() {
if let [single_overload] = overloads.as_ref() {
let function_node = function.node(self.db(), self.file());
if let Some(builder) = self
.context
@ -1157,7 +1160,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// Check that the overloaded function has an implementation. Overload definitions
// within stub files, protocols, and on abstract methods within abstract base classes
// are exempt from this check.
if overloaded.implementation.is_none() && !self.in_stub() {
if implementation.is_none() && !self.in_stub() {
let mut implementation_required = true;
if let NodeWithScopeKind::Class(class_node_ref) = scope {
@ -1169,7 +1172,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if class.is_protocol(self.db())
|| (class.is_abstract(self.db())
&& overloaded.overloads.iter().all(|overload| {
&& overloads.iter().all(|overload| {
overload.has_known_decorator(
self.db(),
FunctionDecorators::ABSTRACT_METHOD,
@ -1199,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let mut decorator_present = false;
let mut decorator_missing = vec![];
for function in overloaded.all() {
for function in overloads.iter().chain(implementation.as_ref()) {
if function.has_known_decorator(self.db(), decorator) {
decorator_present = true;
} else {
@ -1240,8 +1243,8 @@ impl<'db> TypeInferenceBuilder<'db> {
(FunctionDecorators::FINAL, "final"),
(FunctionDecorators::OVERRIDE, "override"),
] {
if let Some(implementation) = overloaded.implementation.as_ref() {
for overload in &overloaded.overloads {
if let Some(implementation) = implementation {
for overload in overloads.as_ref() {
if !overload.has_known_decorator(self.db(), decorator) {
continue;
}
@ -1263,7 +1266,7 @@ impl<'db> TypeInferenceBuilder<'db> {
);
}
} else {
let mut overloads = overloaded.overloads.iter();
let mut overloads = overloads.iter();
let Some(first_overload) = overloads.next() else {
continue;
};
@ -2027,7 +2030,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
let function_kind =
let known_function =
KnownFunction::try_from_definition_and_name(self.db(), definition, name);
let body_scope = self
@ -2035,17 +2038,23 @@ impl<'db> TypeInferenceBuilder<'db> {
.node_scope(NodeWithScopeRef::Function(function))
.to_scope_id(self.db(), self.file());
let inherited_generic_context = None;
let type_mappings = Box::from([]);
let mut inferred_ty = Type::FunctionLiteral(FunctionType::new(
let overload_literal = OverloadLiteral::new(
self.db(),
&name.id,
function_kind,
known_function,
body_scope,
function_decorators,
dataclass_transformer_params,
inherited_generic_context,
);
let inherited_generic_context = None;
let function_literal =
FunctionLiteral::new(self.db(), overload_literal, inherited_generic_context);
let type_mappings = Box::from([]);
let mut inferred_ty = Type::FunctionLiteral(FunctionType::new(
self.db(),
function_literal,
type_mappings,
));
@ -2314,14 +2323,10 @@ impl<'db> TypeInferenceBuilder<'db> {
// overload, or an overload and the implementation both. Nevertheless, this is not
// allowed. We do not try to treat the offenders intelligently -- just use the
// params of the last seen usage of `@dataclass_transform`
if let Some(overloaded) = f.to_overloaded(self.db()) {
overloaded.overloads.iter().for_each(|overload| {
if let Some(params) = overload.dataclass_transformer_params(self.db()) {
dataclass_params = Some(params.into());
}
});
}
if let Some(params) = f.dataclass_transformer_params(self.db()) {
let params = f
.iter_overloads_and_implementation(self.db())
.find_map(|overload| overload.dataclass_transformer_params(self.db()));
if let Some(params) = params {
dataclass_params = Some(params.into());
continue;
}

View file

@ -6,6 +6,7 @@ use crate::semantic_index::predicate::{
};
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
use crate::semantic_index::symbol_table;
use crate::types::function::KnownFunction;
use crate::types::infer::infer_same_file_expression_type;
use crate::types::{
IntersectionBuilder, KnownClass, SubclassOfType, Truthiness, Type, UnionBuilder,
@ -20,7 +21,7 @@ use ruff_python_ast::{BoolOp, ExprBoolOp};
use rustc_hash::FxHashMap;
use std::collections::hash_map::Entry;
use super::{KnownFunction, UnionType};
use super::UnionType;
/// Return the type constraint that `test` (if true) would place on `symbol`, if any.
///

View file

@ -7,9 +7,8 @@ use ruff_python_ast::name::Name;
use crate::{
semantic_index::{symbol_table, use_def_map},
symbol::{symbol_from_bindings, symbol_from_declarations},
types::{
ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance,
},
types::function::KnownFunction,
types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance},
{Db, FxOrderSet},
};

View file

@ -53,10 +53,6 @@ impl<'db> CallableSignature<'db> {
self.overloads.iter()
}
pub(crate) fn as_slice(&self) -> &[Signature<'db>] {
self.overloads.as_slice()
}
pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self {
Self::from_overloads(
self.overloads
@ -1538,7 +1534,8 @@ mod tests {
use super::*;
use crate::db::tests::{TestDb, setup_db};
use crate::symbol::global_symbol;
use crate::types::{FunctionSignature, FunctionType, KnownClass};
use crate::types::KnownClass;
use crate::types::function::FunctionType;
use ruff_db::system::DbWithWritableSystem as _;
#[track_caller]
@ -1559,9 +1556,11 @@ mod tests {
fn empty() {
let mut db = setup_db();
db.write_dedented("/src/a.py", "def f(): ...").unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
assert!(sig.return_ty.is_none());
assert_params(&sig, &[]);
@ -1582,9 +1581,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
assert_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes");
assert_params(
@ -1633,9 +1634,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@ -1669,9 +1672,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.pyi");
let func = get_function_f(&db, "/src/a.pyi")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@ -1705,9 +1710,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let func = get_function_f(&db, "/src/a.py")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@ -1751,9 +1758,11 @@ mod tests {
",
)
.unwrap();
let func = get_function_f(&db, "/src/a.pyi");
let func = get_function_f(&db, "/src/a.pyi")
.literal(&db)
.last_definition(&db);
let sig = func.internal_signature(&db, None);
let sig = func.signature(&db, None);
let [
Parameter {
@ -1789,15 +1798,13 @@ mod tests {
.unwrap();
let func = get_function_f(&db, "/src/a.py");
let expected_sig = func.internal_signature(&db, None);
let overload = func.literal(&db).last_definition(&db);
let expected_sig = overload.signature(&db, None);
// With no decorators, internal and external signature are the same
assert_eq!(
func.signature(&db),
&FunctionSignature {
overloads: CallableSignature::single(expected_sig),
implementation: None
},
&CallableSignature::single(expected_sig)
);
}
}