Skip to content

Commit efeadb1

Browse files
arthaudmeta-codesync[bot]
authored andcommitted
Add full module and class to function metadata
Summary: # Problem When building call graphs for pysa, we want to fully identify a function given a `Type::Function` or `Type::Overload`. While `FuncMetadata` contains the module name and class name, this is not enough to uniquely identify the function, because multiple modules can have the same name, and multiple class can have the same name within a module. # Solution Instead of storing `ModuleName` and `Name` for the class, let's store `Module` and `Class`, which fully identify a module and class. One downside is that it is not possible to create a `FuncId` from a `FunctionKind`, but we can work around that. This also requires a little bit of boilerplate to define `PartialEq`, `Hash`, `PartialOrd` on `FuncId`. Reviewed By: stroxler Differential Revision: D85441657 fbshipit-source-id: 9277543fadf011047024c971f27f912e4bd03238
1 parent 2766a03 commit efeadb1

File tree

22 files changed

+261
-224
lines changed

22 files changed

+261
-224
lines changed

crates/pyrefly_types/src/callable.rs

Lines changed: 137 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,31 @@
66
*/
77

88
use std::borrow::Cow;
9+
use std::cmp::Ord;
10+
use std::cmp::Ordering;
911
use std::fmt;
1012
use std::fmt::Display;
13+
use std::hash::Hash;
14+
use std::hash::Hasher;
1115

16+
use dupe::Dupe;
1217
use pyrefly_derive::TypeEq;
1318
use pyrefly_derive::Visit;
1419
use pyrefly_derive::VisitMut;
1520
use pyrefly_python::dunder;
21+
use pyrefly_python::module::Module;
1622
use pyrefly_python::module_name::ModuleName;
23+
use pyrefly_python::module_path::ModulePath;
1724
use pyrefly_util::display::commas_iter;
1825
use pyrefly_util::prelude::VecExt;
26+
use pyrefly_util::visit::Visit;
1927
use pyrefly_util::visit::VisitMut;
2028
use ruff_python_ast::Keyword;
2129
use ruff_python_ast::name::Name;
2230

31+
use crate::class::Class;
2332
use crate::class::ClassType;
33+
use crate::equality::TypeEq;
2434
use crate::keywords::DataclassTransformKeywords;
2535
use crate::types::Type;
2636

@@ -297,12 +307,12 @@ pub struct FuncMetadata {
297307
}
298308

299309
impl FuncMetadata {
300-
pub fn def(module: ModuleName, cls: Name, func: Name) -> Self {
310+
pub fn def(module: Module, cls: Class, func: Name) -> Self {
301311
Self {
302312
kind: FunctionKind::Def(Box::new(FuncId {
303313
module,
304314
cls: Some(cls),
305-
func,
315+
name: func,
306316
})),
307317
flags: FuncFlags::default(),
308318
}
@@ -341,29 +351,89 @@ pub struct FuncFlags {
341351
pub dataclass_transform_metadata: Option<DataclassTransformKeywords>,
342352
}
343353

344-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
345-
#[derive(Visit, VisitMut, TypeEq)]
354+
#[derive(Debug, Clone)]
346355
pub struct FuncId {
347-
pub module: ModuleName,
348-
pub cls: Option<Name>,
349-
pub func: Name,
356+
pub module: Module,
357+
pub cls: Option<Class>,
358+
pub name: Name,
359+
}
360+
361+
impl PartialEq for FuncId {
362+
fn eq(&self, other: &Self) -> bool {
363+
self.key_eq().eq(&other.key_eq())
364+
}
365+
}
366+
367+
impl Eq for FuncId {}
368+
impl TypeEq for FuncId {}
369+
370+
impl Ord for FuncId {
371+
fn cmp(&self, other: &Self) -> Ordering {
372+
self.key_ord().cmp(&other.key_ord())
373+
}
374+
}
375+
376+
impl PartialOrd for FuncId {
377+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
378+
Some(self.cmp(other))
379+
}
380+
}
381+
382+
impl Hash for FuncId {
383+
fn hash<H: Hasher>(&self, state: &mut H) {
384+
self.key_eq().hash(state)
385+
}
386+
}
387+
388+
impl VisitMut<Type> for FuncId {
389+
fn recurse_mut(&mut self, _: &mut dyn FnMut(&mut Type)) {}
390+
}
391+
impl Visit<Type> for FuncId {
392+
fn recurse<'a>(&'a self, _: &mut dyn FnMut(&'a Type)) {}
350393
}
351394

352395
impl FuncId {
353-
pub fn format(&self, current_module: ModuleName) -> String {
396+
fn key_eq(&self) -> (ModuleName, ModulePath, Option<Class>, &Name) {
397+
(
398+
self.module.name(),
399+
self.module.path().to_key_eq(),
400+
self.cls.clone(),
401+
&self.name,
402+
)
403+
}
404+
405+
fn key_ord(&self) -> (ModuleName, ModulePath, Option<Class>, &Name) {
406+
self.key_eq()
407+
}
408+
409+
fn format_impl(
410+
func_module: ModuleName,
411+
func_cls: Option<Class>,
412+
func_name: &Name,
413+
current_module: ModuleName,
414+
) -> String {
354415
let module_prefix =
355-
if self.module == current_module || self.module == ModuleName::builtins() {
416+
if func_module == current_module || func_module == ModuleName::builtins() {
356417
"".to_owned()
357418
} else {
358-
format!("{}.", self.module)
419+
format!("{}.", func_module)
359420
};
360-
let class_prefix = match &self.cls {
421+
let class_prefix = match &func_cls {
361422
Some(cls) => {
362-
format!("{cls}.")
423+
format!("{}.", cls.name())
363424
}
364425
None => "".to_owned(),
365426
};
366-
format!("{module_prefix}{class_prefix}{}", self.func)
427+
format!("{module_prefix}{class_prefix}{}", func_name)
428+
}
429+
430+
pub fn format(&self, current_module: ModuleName) -> String {
431+
Self::format_impl(
432+
self.module.name(),
433+
self.cls.clone(),
434+
&self.name,
435+
current_module,
436+
)
367437
}
368438
}
369439

@@ -628,8 +698,8 @@ impl Display for Param {
628698
}
629699

630700
impl FunctionKind {
631-
pub fn from_name(module: ModuleName, cls: Option<&Name>, func: &Name) -> Self {
632-
match (module.as_str(), cls, func.as_str()) {
701+
pub fn from_name(module: Module, cls: Option<Class>, func: &Name) -> Self {
702+
match (module.name().as_str(), cls.as_ref(), func.as_str()) {
633703
("builtins", None, "isinstance") => Self::IsInstance,
634704
("builtins", None, "issubclass") => Self::IsSubclass,
635705
("builtins", None, "classmethod") => Self::ClassMethod,
@@ -648,95 +718,31 @@ impl FunctionKind {
648718
("functools", None, "total_ordering") => Self::TotalOrdering,
649719
_ => Self::Def(Box::new(FuncId {
650720
module,
651-
cls: cls.cloned(),
652-
func: func.clone(),
721+
cls,
722+
name: func.clone(),
653723
})),
654724
}
655725
}
656726

657-
pub fn as_func_id(&self) -> FuncId {
727+
pub fn module_name(&self) -> ModuleName {
658728
match self {
659-
Self::IsInstance => FuncId {
660-
module: ModuleName::builtins(),
661-
cls: None,
662-
func: Name::new_static("isinstance"),
663-
},
664-
Self::IsSubclass => FuncId {
665-
module: ModuleName::builtins(),
666-
cls: None,
667-
func: Name::new_static("issubclass"),
668-
},
669-
Self::ClassMethod => FuncId {
670-
module: ModuleName::builtins(),
671-
cls: None,
672-
func: Name::new_static("classmethod"),
673-
},
674-
Self::Dataclass => FuncId {
675-
module: ModuleName::dataclasses(),
676-
cls: None,
677-
func: Name::new_static("dataclass"),
678-
},
679-
Self::DataclassField => FuncId {
680-
module: ModuleName::dataclasses(),
681-
cls: None,
682-
func: Name::new_static("field"),
683-
},
684-
Self::DataclassTransform => FuncId {
685-
module: ModuleName::typing(),
686-
cls: None,
687-
func: Name::new_static("dataclass_transform"),
688-
},
689-
Self::Final => FuncId {
690-
module: ModuleName::typing(),
691-
cls: None,
692-
func: Name::new_static("final"),
693-
},
694-
Self::Overload => FuncId {
695-
module: ModuleName::typing(),
696-
cls: None,
697-
func: Name::new_static("overload"),
698-
},
699-
Self::Override => FuncId {
700-
module: ModuleName::typing(),
701-
cls: None,
702-
func: Name::new_static("override"),
703-
},
704-
Self::Cast => FuncId {
705-
module: ModuleName::typing(),
706-
cls: None,
707-
func: Name::new_static("cast"),
708-
},
709-
Self::AssertType => FuncId {
710-
module: ModuleName::typing(),
711-
cls: None,
712-
func: Name::new_static("assert_type"),
713-
},
714-
Self::RevealType => FuncId {
715-
module: ModuleName::typing(),
716-
cls: None,
717-
func: Name::new_static("reveal_type"),
718-
},
719-
Self::RuntimeCheckable => FuncId {
720-
module: ModuleName::typing(),
721-
cls: None,
722-
func: Name::new_static("runtime_checkable"),
723-
},
724-
Self::CallbackProtocol(cls) => FuncId {
725-
module: cls.qname().module_name(),
726-
cls: Some(cls.name().clone()),
727-
func: dunder::CALL,
728-
},
729-
Self::AbstractMethod => FuncId {
730-
module: ModuleName::abc(),
731-
cls: None,
732-
func: Name::new_static("abstractmethod"),
733-
},
734-
Self::TotalOrdering => FuncId {
735-
module: ModuleName::functools(),
736-
cls: None,
737-
func: Name::new_static("total_ordering"),
738-
},
739-
Self::Def(func_id) => (**func_id).clone(),
729+
Self::IsInstance => ModuleName::builtins(),
730+
Self::IsSubclass => ModuleName::builtins(),
731+
Self::ClassMethod => ModuleName::builtins(),
732+
Self::Dataclass => ModuleName::dataclasses(),
733+
Self::DataclassField => ModuleName::dataclasses(),
734+
Self::DataclassTransform => ModuleName::typing(),
735+
Self::Final => ModuleName::typing(),
736+
Self::Overload => ModuleName::typing(),
737+
Self::Override => ModuleName::typing(),
738+
Self::Cast => ModuleName::typing(),
739+
Self::AssertType => ModuleName::typing(),
740+
Self::RevealType => ModuleName::typing(),
741+
Self::RuntimeCheckable => ModuleName::typing(),
742+
Self::CallbackProtocol(cls) => cls.qname().module_name(),
743+
Self::AbstractMethod => ModuleName::abc(),
744+
Self::TotalOrdering => ModuleName::functools(),
745+
Self::Def(func_id) => func_id.module.name().dupe(),
740746
}
741747
}
742748

@@ -758,9 +764,40 @@ impl FunctionKind {
758764
Self::CallbackProtocol(_) => Cow::Owned(dunder::CALL),
759765
Self::AbstractMethod => Cow::Owned(Name::new_static("abstractmethod")),
760766
Self::TotalOrdering => Cow::Owned(Name::new_static("total_ordering")),
761-
Self::Def(func_id) => Cow::Borrowed(&func_id.func),
767+
Self::Def(func_id) => Cow::Borrowed(&func_id.name),
762768
}
763769
}
770+
771+
pub fn class(&self) -> Option<Class> {
772+
match self {
773+
Self::IsInstance => None,
774+
Self::IsSubclass => None,
775+
Self::ClassMethod => None,
776+
Self::Dataclass => None,
777+
Self::DataclassField => None,
778+
Self::DataclassTransform => None,
779+
Self::Final => None,
780+
Self::Overload => None,
781+
Self::Override => None,
782+
Self::Cast => None,
783+
Self::AssertType => None,
784+
Self::RevealType => None,
785+
Self::RuntimeCheckable => None,
786+
Self::CallbackProtocol(cls) => Some(cls.class_object().dupe()),
787+
Self::AbstractMethod => None,
788+
Self::TotalOrdering => None,
789+
Self::Def(func_id) => func_id.cls.clone(),
790+
}
791+
}
792+
793+
pub fn format(&self, current_module: ModuleName) -> String {
794+
FuncId::format_impl(
795+
self.module_name(),
796+
self.class(),
797+
self.function_name().as_ref(),
798+
current_module,
799+
)
800+
}
764801
}
765802

766803
pub fn unexpected_keyword(error: &dyn Fn(String), func: &str, keyword: &Keyword) {

0 commit comments

Comments
 (0)