Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/pyrefly_types/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,24 @@ impl<'a> TypeDisplayContext<'a> {
output.write_str(ta.name.as_str())
}
}
Type::Forall(box Forall {
tparams,
body: Forallable::ParamSpecValue(_),
}) => {
output.write_str("[")?;
output.write_str(&format!("{}", commas_iter(|| tparams.iter())))?;
output.write_str("]")?;
output.write_str("<ParamSpecValue>")
}
Type::Forall(box Forall {
tparams,
body: Forallable::Concatenate(_, _),
}) => {
output.write_str("[")?;
output.write_str(&format!("{}", commas_iter(|| tparams.iter())))?;
output.write_str("]")?;
output.write_str("<Concatenate>")
}
Type::Type(ty) => {
output.write_str("type[")?;
self.fmt_helper_generic(ty, false, output)?;
Expand Down
44 changes: 43 additions & 1 deletion crates/pyrefly_types/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,11 +548,43 @@ impl Forall<Forallable> {

/// These are things that can have Forall around them, so often you see `Forall<Forallable>`
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Visit, VisitMut, TypeEq)]
#[derive(TypeEq)]
pub enum Forallable {
TypeAlias(TypeAlias),
Function(Function),
Callable(Callable),
ParamSpecValue(ParamList),
Concatenate(Box<[Type]>, Box<Forallable>),
}

impl Visit<Type> for Forallable {
fn recurse<'a>(&'a self, f: &mut dyn FnMut(&'a Type)) {
match self {
Self::TypeAlias(ta) => ta.visit(f),
Self::Function(func) => func.visit(f),
Self::Callable(callable) => callable.visit(f),
Self::ParamSpecValue(params) => params.visit(f),
Self::Concatenate(args, inner) => {
args.visit(f);
inner.visit(f);
}
}
}
}

impl VisitMut<Type> for Forallable {
fn recurse_mut(&mut self, f: &mut dyn FnMut(&mut Type)) {
match self {
Self::TypeAlias(ta) => ta.visit_mut(f),
Self::Function(func) => func.visit_mut(f),
Self::Callable(callable) => callable.visit_mut(f),
Self::ParamSpecValue(params) => params.visit_mut(f),
Self::Concatenate(args, inner) => {
args.visit_mut(f);
inner.visit_mut(f);
}
}
}
}

impl Forallable {
Expand All @@ -572,6 +604,8 @@ impl Forallable {
Self::Function(func) => func.metadata.kind.function_name(),
Self::Callable(_) => Cow::Owned(Name::new_static("<callable>")),
Self::TypeAlias(ta) => Cow::Borrowed(&ta.name),
Self::ParamSpecValue(_) => Cow::Owned(Name::new_static("<paramspec_value>")),
Self::Concatenate(_, _) => Cow::Owned(Name::new_static("<concatenate>")),
}
}

Expand All @@ -580,6 +614,10 @@ impl Forallable {
Self::Function(func) => Type::Function(Box::new(func)),
Self::Callable(callable) => Type::Callable(Box::new(callable)),
Self::TypeAlias(ta) => Type::TypeAlias(Box::new(ta)),
Self::ParamSpecValue(params) => Type::ParamSpecValue(params),
Self::Concatenate(args, paramspec) => {
Type::Concatenate(args, Box::new(paramspec.as_type()))
}
}
}

Expand All @@ -588,6 +626,8 @@ impl Forallable {
Self::Function(func) => func.signature.is_typeguard(),
Self::Callable(callable) => callable.is_typeguard(),
Self::TypeAlias(_) => false,
Self::ParamSpecValue(_) => false,
Self::Concatenate(_, _) => false,
}
}

Expand All @@ -596,6 +636,8 @@ impl Forallable {
Self::Function(func) => func.signature.is_typeis(),
Self::Callable(callable) => callable.is_typeis(),
Self::TypeAlias(_) => false,
Self::ParamSpecValue(_) => false,
Self::Concatenate(_, _) => false,
}
}
}
Expand Down
20 changes: 12 additions & 8 deletions pyrefly/lib/alt/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub enum CallTarget {
/// Method of a class. The `Type` is the self/cls argument.
BoundMethod(Type, TargetWithTParams<Function>),
/// A class object.
Class(ClassType, ConstructorKind),
Class(TargetWithTParams<ClassType>, ConstructorKind),
/// A TypedDict.
TypedDict(TypedDict),
/// An overloaded function.
Expand Down Expand Up @@ -213,7 +213,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
Type::ClassDef(cls) => match self.instantiate(&cls) {
// `instantiate` can only return `ClassType` or `TypedDict`
Type::ClassType(cls) => CallTargetLookup::Ok(Box::new(CallTarget::Class(
cls,
TargetWithTParams(None, cls),
ConstructorKind::BareClassName,
))),
Type::TypedDict(typed_dict) => {
Expand All @@ -223,13 +223,16 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
},
Type::Type(box Type::ClassType(cls)) | Type::Type(box Type::SelfType(cls)) => {
CallTargetLookup::Ok(Box::new(CallTarget::Class(
cls,
TargetWithTParams(None, cls),
ConstructorKind::TypeOfClass,
)))
}
Type::Type(box Type::Tuple(tuple)) => {
CallTargetLookup::Ok(Box::new(CallTarget::Class(
TargetWithTParams(None, self.erase_tuple_type(tuple)),
ConstructorKind::TypeOfClass,
)))
}
Type::Type(box Type::Tuple(tuple)) => CallTargetLookup::Ok(Box::new(
CallTarget::Class(self.erase_tuple_type(tuple), ConstructorKind::TypeOfClass),
)),
Type::Type(box Type::Quantified(quantified)) => {
CallTargetLookup::Ok(Box::new(CallTarget::Callable(TargetWithTParams(
None,
Expand All @@ -249,7 +252,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
match &mut target {
CallTargetLookup::Ok(
box (CallTarget::Callable(TargetWithTParams(x, _))
| CallTarget::Function(TargetWithTParams(x, _))),
| CallTarget::Function(TargetWithTParams(x, _))
| CallTarget::Class(TargetWithTParams(x, _), _)),
) => {
*x = Some(forall.tparams);
}
Expand Down Expand Up @@ -756,7 +760,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
}
};
let res = match call_target {
CallTarget::Class(cls, constructor_kind) => {
CallTarget::Class(TargetWithTParams(_tparams, cls), constructor_kind) => {
if cls.has_qname("typing", "Any") {
return self.error(
errors,
Expand Down
33 changes: 33 additions & 0 deletions pyrefly/lib/alt/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use crate::types::callable::Params;
use crate::types::callable::Required;
use crate::types::quantified::Quantified;
use crate::types::tuple::Tuple;
use crate::types::types::Forallable;
use crate::types::types::Type;
use crate::types::types::Var;

Expand Down Expand Up @@ -1043,6 +1044,38 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
Params::ParamSpec(concatenate, p) => {
let p = self.solver().expand_vars(p);
match p {
Type::Forall(box forall)
if matches!(forall.body, Forallable::ParamSpecValue(_)) =>
{
// Instantiate fresh type variables for the Forall-wrapped ParamSpecValue
if let Forallable::ParamSpecValue(params_before) = forall.body {
let (_qs_params, instantiated_type) = self.solver().fresh_quantified(
&forall.tparams,
Type::ParamSpecValue(params_before),
self.uniques,
);

// Extract the instantiated params
if let Type::ParamSpecValue(params) = instantiated_type {
self.callable_infer_params(
callable_name,
&params.prepend_types(&concatenate),
None,
self_arg,
args,
keywords,
range,
arg_errors,
call_errors,
context,
)
} else {
unreachable!()
}
} else {
unreachable!()
}
}
Type::ParamSpecValue(params) => self.callable_infer_params(
callable_name,
&params.prepend_types(&concatenate),
Expand Down
10 changes: 10 additions & 0 deletions pyrefly/lib/alt/class/targs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,16 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
.1
}

/// Instantiates a class with fresh variables, returning both the handle and the type.
/// Used when the caller needs to finalize the quantified variables.
pub fn instantiate_fresh_class_with_handle(&self, cls: &Class) -> (QuantifiedHandle, Type) {
self.solver().fresh_quantified(
&self.get_class_tparams(cls),
self.instantiate(cls),
self.uniques,
)
}

pub fn instantiate_fresh_tuple(&self) -> Type {
let quantified = Quantified::type_var_tuple(Name::new_static("Ts"), self.uniques, None);
let tparams = TParams::new(vec![TParam {
Expand Down
2 changes: 2 additions & 0 deletions pyrefly/lib/alt/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
}))
}),
Forallable::TypeAlias(_) => None,
Forallable::ParamSpecValue(_) => None,
Forallable::Concatenate(_, _) => None,
},
Type::Callable(callable) => callable
.split_first_param()
Expand Down
2 changes: 2 additions & 0 deletions pyrefly/lib/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,8 @@ impl<'a> CalleesWithLocation<'a> {
Forallable::TypeAlias(t) => {
self.callee_from_type(&t.as_type(), call_target, callee_range, call_arguments)
}
Forallable::ParamSpecValue(_) => vec![],
Forallable::Concatenate(_, _) => vec![],
},
Type::SelfType(c) | Type::ClassType(c) => {
self.callee_from_mro(c.class_object(), "__call__", |_solver, c| {
Expand Down
5 changes: 4 additions & 1 deletion pyrefly/lib/report/pysa/call_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1690,7 +1690,10 @@ impl<'a> CallGraphVisitor<'a> {
)
.into_call_callees()
}
Some(CallTargetLookup::Ok(box crate::alt::call::CallTarget::Class(class_type, _))) => {
Some(CallTargetLookup::Ok(box crate::alt::call::CallTarget::Class(
crate::alt::call::TargetWithTParams(_tparams, class_type),
_,
))) => {
// Constructing a class instance.
let (init_method, new_method) = self
.module_context
Expand Down
Loading