diff --git a/pyrefly/lib/alt/answers_solver.rs b/pyrefly/lib/alt/answers_solver.rs index 4425b56b06..4c9bb375c7 100644 --- a/pyrefly/lib/alt/answers_solver.rs +++ b/pyrefly/lib/alt/answers_solver.rs @@ -39,6 +39,7 @@ use crate::alt::answers::SolutionsEntry; use crate::alt::answers::SolutionsTable; use crate::alt::traits::Solve; use crate::binding::binding::AnyIdx; +use crate::config::error_kind::ErrorKind; use crate::binding::binding::Binding; use crate::binding::binding::Exported; use crate::binding::binding::KeyExport; @@ -871,4 +872,32 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { pub fn error_swallower(&self) -> ErrorCollector { ErrorCollector::new(self.module().dupe(), ErrorStyle::Never) } + + /// Add an implicit-any error for a generic class without explicit type arguments. + pub fn add_implicit_any_error( + errors: &ErrorCollector, + range: TextRange, + class_name: &str, + tparam_name: Option<&str>, + ) { + let msg = if let Some(tparam) = tparam_name { + format!( + "Cannot determine the type parameter `{}` for generic class `{}`", + tparam, class_name, + ) + } else { + format!( + "Cannot determine the type parameter for generic class `{}`", + class_name + ) + }; + errors.add( + range, + ErrorInfo::Kind(ErrorKind::ImplicitAny), + vec1![ + msg, + "Either specify the type argument explicitly, or specify a default for the type variable.".to_owned(), + ], + ); + } } diff --git a/pyrefly/lib/alt/class/targs.rs b/pyrefly/lib/alt/class/targs.rs index 32ffc70e2b..c2519b8df1 100644 --- a/pyrefly/lib/alt/class/targs.rs +++ b/pyrefly/lib/alt/class/targs.rs @@ -15,7 +15,6 @@ use pyrefly_util::prelude::SliceExt; use ruff_python_ast::name::Name; use ruff_text_size::TextRange; use starlark_map::small_map::SmallMap; -use vec1::vec1; use crate::alt::answers::LookupAnswer; use crate::alt::answers_solver::AnswersSolver; @@ -170,17 +169,11 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { let targs = self.create_default_targs( self.get_class_tparams(cls), Some(&|tparam: &TParam| { - errors.add( + Self::add_implicit_any_error( + errors, range, - ErrorInfo::Kind(ErrorKind::ImplicitAny), - vec1![ - format!( - "Cannot determine the type parameter `{}` for generic class `{}`", - tparam.name(), - cls.name(), - ), - "Either specify the type argument explicitly, or specify a default for the type variable.".to_owned(), - ], + cls.name().as_str(), + Some(tparam.name().as_str()), ); }), ); diff --git a/pyrefly/lib/alt/expr.rs b/pyrefly/lib/alt/expr.rs index 98c7ffa906..869da7059c 100644 --- a/pyrefly/lib/alt/expr.rs +++ b/pyrefly/lib/alt/expr.rs @@ -1278,16 +1278,20 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { ) -> Type { ty.transform(&mut |ty| match ty { Type::SpecialForm(SpecialForm::Tuple) => { + Self::add_implicit_any_error(errors, range, "tuple", None); *ty = Type::unbounded_tuple(Type::Any(AnyStyle::Implicit)); } Type::SpecialForm(SpecialForm::Callable) => { + Self::add_implicit_any_error(errors, range, "Callable", None); *ty = Type::callable_ellipsis(Type::Any(AnyStyle::Implicit)) } Type::SpecialForm(SpecialForm::Type) => { + Self::add_implicit_any_error(errors, range, "type", None); *ty = Type::type_form(Type::Any(AnyStyle::Implicit)) } Type::ClassDef(cls) => { if cls.is_builtin("tuple") { + Self::add_implicit_any_error(errors, range, "tuple", None); *ty = Type::type_form(Type::unbounded_tuple(Type::Any(AnyStyle::Implicit))); } else if cls.has_toplevel_qname("typing", "Any") { *ty = Type::type_form(Type::any_explicit()) diff --git a/pyrefly/lib/test/generic_basic.rs b/pyrefly/lib/test/generic_basic.rs index ebbe76c9ee..552c2190c1 100644 --- a/pyrefly/lib/test/generic_basic.rs +++ b/pyrefly/lib/test/generic_basic.rs @@ -533,3 +533,26 @@ def _to_list[T]( def to_type[T](value: Any, kind: TypeForm[T]) -> T: ... "#, ); + +// https://github.com/facebook/pyrefly/issues/1970 +testcase!( + test_implicit_any_for_special_forms, + TestEnv::new().enable_implicit_any_error(), + r#" +from typing import Callable, Type + +def f( + x: list, # E: Cannot determine the type parameter `_T` for generic class `list` + y: tuple, # E: Cannot determine the type parameter for generic class `tuple` + z: Callable, # E: Cannot determine the type parameter for generic class `Callable` + w: Type, # E: Cannot determine the type parameter for generic class `type` +): + pass + +# Note: bare builtin `type` annotation doesn't trigger implicit-any yet because +# the `type` class is not defined as generic in typeshed. `typing.Type` works +# because it's handled as a special form. +def g(t: type): + pass + "#, +);