diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index c89e8f0092..ca3ee322a2 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -366,9 +366,15 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { // If the class has an unknown base (e.g. inherits from an // unresolved name), it might have inherited `__call__` from // that base, so treat it as callable with implicit Any. + // + // `NotImplemented` is a singleton instance of `NotImplementedType`; it must + // never be treated as callable even when stubs use `NotImplementedType(Any)`, + // which would otherwise set `has_base_any` and hit this branch. None if self .get_metadata_for_class(cls.class_object()) - .has_base_any() => + .has_base_any() + && !cls.has_qname("types", "NotImplementedType") + && !cls.has_qname("builtins", "_NotImplementedType") => { CallTargetLookup::Ok(Box::new(CallTarget::Any(AnyStyle::Implicit))) } diff --git a/pyrefly/lib/test/callable.rs b/pyrefly/lib/test/callable.rs index 86dc0da691..8ed8716684 100644 --- a/pyrefly/lib/test/callable.rs +++ b/pyrefly/lib/test/callable.rs @@ -1449,3 +1449,13 @@ def after_func() -> None: ... schedule(1000, after_func) "#, ); + +// Regression test for https://github.com/facebook/pyrefly/issues/2918 +testcase!( + test_notimplemented_not_callable, + r#" +NotImplemented() # E: Expected a callable +NotImplemented("not yet done") # E: Expected a callable +raise NotImplementedError() +"#, +); diff --git a/pyrefly/lib/test/calls.rs b/pyrefly/lib/test/calls.rs index 9c5bee4b0e..20c8c3c402 100644 --- a/pyrefly/lib/test/calls.rs +++ b/pyrefly/lib/test/calls.rs @@ -408,16 +408,15 @@ def get_flow_version(run_id: str | None) -> str | None: // https://github.com/facebook/pyrefly/issues/2918 testcase!( - bug = "Should error when calling NotImplemented (a constant, not a class)", test_call_not_implemented_constant, r#" # NotImplemented is a singleton constant, not a callable class. # Using NotImplemented() is always a mistake; they mean NotImplementedError(). def broken(): - raise NotImplemented() + raise NotImplemented() # E: Expected a callable def also_broken(): - raise NotImplemented("not yet done") + raise NotImplemented("not yet done") # E: Expected a callable "#, );