Skip to content

Commit 02f67b8

Browse files
fangyi-zhoufacebook-github-bot
authored andcommitted
Warn if deprecated function is referred (#1011)
Summary: This diff implements the last missing feature for deprecation warnings (#376): to warn when the deprecated function is referred (e.g. passed as function reference). The implementation checks when typechecking an expression, whether the type of an expression is a deprecated function/bound method. If that's the case, a deprecated warning is generated. If we only add this check, we might run into the issue that a deprecated function call would generate 2 deprecation warnings, one when the function is typechecked, and another when the call is typechecked. To avoid the duplication, we avoid emitting the deprecation warning when typechecking a function call, instead we emit the deprecation warning when generating the call target. As a consequence, the error message of deprecation warning is changed from "Calling" a deprecated function to "Reference to" a deprecated function. Suggestions are welcome for how to improve clarify of this error message. Pull Request resolved: #1011 Test Plan: Imported from GitHub, without a `Test Plan:` line. Rollback Plan: Reviewed By: rchen152 Differential Revision: D81681196 Pulled By: yangdanny97 fbshipit-source-id: 8d106589c7ce85cf8daa3c0f5992401edb448531
1 parent 2ef5a54 commit 02f67b8

File tree

8 files changed

+111
-53
lines changed

8 files changed

+111
-53
lines changed

conformance/third_party/conformance.exp

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3973,14 +3973,24 @@
39733973
},
39743974
{
39753975
"code": -2,
3976-
"column": 23,
3977-
"concise_description": "Call to deprecated function `_directives_deprecated_library.norwegian_blue`",
3978-
"description": "Call to deprecated function `_directives_deprecated_library.norwegian_blue`",
3976+
"column": 1,
3977+
"concise_description": "`_directives_deprecated_library.norwegian_blue` is deprecated",
3978+
"description": "`_directives_deprecated_library.norwegian_blue` is deprecated",
39793979
"line": 24,
39803980
"name": "deprecated",
3981-
"stop_column": 26,
3981+
"stop_column": 23,
39823982
"stop_line": 24
39833983
},
3984+
{
3985+
"code": -2,
3986+
"column": 5,
3987+
"concise_description": "`_directives_deprecated_library.norwegian_blue` is deprecated",
3988+
"description": "`_directives_deprecated_library.norwegian_blue` is deprecated",
3989+
"line": 25,
3990+
"name": "deprecated",
3991+
"stop_column": 27,
3992+
"stop_line": 25
3993+
},
39843994
{
39853995
"code": -2,
39863996
"column": 12,
@@ -3995,7 +4005,7 @@
39954005
"code": -2,
39964006
"column": 5,
39974007
"concise_description": "`+` is not supported between `Spam` and `Literal[1]`",
3998-
"description": "`+` is not supported between `Spam` and `Literal[1]`\n Call to deprecated function `_directives_deprecated_library.Spam.__add__`",
4008+
"description": "`+` is not supported between `Spam` and `Literal[1]`\n `_directives_deprecated_library.Spam.__add__` is deprecated",
39994009
"line": 41,
40004010
"name": "unsupported-operation",
40014011
"stop_column": 13,
@@ -4005,7 +4015,7 @@
40054015
"code": -2,
40064016
"column": 1,
40074017
"concise_description": "`+=` is not supported between `Spam` and `Literal[1]`",
4008-
"description": "`+=` is not supported between `Spam` and `Literal[1]`\n Call to deprecated function `_directives_deprecated_library.Spam.__add__`",
4018+
"description": "`+=` is not supported between `Spam` and `Literal[1]`\n `_directives_deprecated_library.Spam.__add__` is deprecated",
40094019
"line": 42,
40104020
"name": "unsupported-operation",
40114021
"stop_column": 10,
@@ -4014,8 +4024,8 @@
40144024
{
40154025
"code": -2,
40164026
"column": 1,
4017-
"concise_description": "Call to deprecated function `_directives_deprecated_library.Spam.greasy`",
4018-
"description": "Call to deprecated function `_directives_deprecated_library.Spam.greasy`",
4027+
"concise_description": "`_directives_deprecated_library.Spam.greasy` is deprecated",
4028+
"description": "`_directives_deprecated_library.Spam.greasy` is deprecated",
40194029
"line": 44,
40204030
"name": "deprecated",
40214031
"stop_column": 12,
@@ -4024,8 +4034,8 @@
40244034
{
40254035
"code": -2,
40264036
"column": 1,
4027-
"concise_description": "Call to deprecated function `_directives_deprecated_library.Spam.shape`",
4028-
"description": "Call to deprecated function `_directives_deprecated_library.Spam.shape`",
4037+
"concise_description": "`_directives_deprecated_library.Spam.shape` is deprecated",
4038+
"description": "`_directives_deprecated_library.Spam.shape` is deprecated",
40294039
"line": 47,
40304040
"name": "deprecated",
40314041
"stop_column": 11,
@@ -4034,41 +4044,41 @@
40344044
{
40354045
"code": -2,
40364046
"column": 1,
4037-
"concise_description": "Call to deprecated function `_directives_deprecated_library.Spam.shape`",
4038-
"description": "Call to deprecated function `_directives_deprecated_library.Spam.shape`",
4047+
"concise_description": "`_directives_deprecated_library.Spam.shape` is deprecated",
4048+
"description": "`_directives_deprecated_library.Spam.shape` is deprecated",
40394049
"line": 48,
40404050
"name": "deprecated",
40414051
"stop_column": 11,
40424052
"stop_line": 48
40434053
},
40444054
{
40454055
"code": -2,
4046-
"column": 10,
4047-
"concise_description": "Call to deprecated function `Invocable.__call__`",
4048-
"description": "Call to deprecated function `Invocable.__call__`",
4056+
"column": 1,
4057+
"concise_description": "`Invocable.__call__` is deprecated",
4058+
"description": "`Invocable.__call__` is deprecated",
40494059
"line": 58,
40504060
"name": "deprecated",
4051-
"stop_column": 12,
4061+
"stop_column": 10,
40524062
"stop_line": 58
40534063
},
40544064
{
40554065
"code": -2,
4056-
"column": 6,
4057-
"concise_description": "Call to deprecated function `lorem`",
4058-
"description": "Call to deprecated function `lorem`",
4066+
"column": 1,
4067+
"concise_description": "`lorem` is deprecated",
4068+
"description": "`lorem` is deprecated",
40594069
"line": 69,
40604070
"name": "deprecated",
4061-
"stop_column": 8,
4071+
"stop_column": 6,
40624072
"stop_line": 69
40634073
},
40644074
{
40654075
"code": -2,
4066-
"column": 10,
4067-
"concise_description": "Call to deprecated function `SupportsFoo1.foo`",
4068-
"description": "Call to deprecated function `SupportsFoo1.foo`",
4076+
"column": 5,
4077+
"concise_description": "`SupportsFoo1.foo` is deprecated",
4078+
"description": "`SupportsFoo1.foo` is deprecated",
40694079
"line": 98,
40704080
"name": "deprecated",
4071-
"stop_column": 12,
4081+
"stop_column": 10,
40724082
"stop_line": 98
40734083
}
40744084
],

conformance/third_party/conformance.result

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,7 @@
138138
"dataclasses_usage.py": [],
139139
"directives_assert_type.py": [],
140140
"directives_cast.py": [],
141-
"directives_deprecated.py": [
142-
"Line 25: Expected 1 errors"
143-
],
141+
"directives_deprecated.py": [],
144142
"directives_no_type_check.py": [],
145143
"directives_reveal_type.py": [],
146144
"directives_type_checking.py": [],

conformance/third_party/results.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"total": 138,
3-
"pass": 87,
4-
"fail": 51,
5-
"pass_rate": 0.63,
6-
"differences": 233,
3+
"pass": 88,
4+
"fail": 50,
5+
"pass_rate": 0.64,
6+
"differences": 232,
77
"passing": [
88
"aliases_explicit.py",
99
"aliases_newtype.py",
@@ -34,6 +34,7 @@
3434
"dataclasses_usage.py",
3535
"directives_assert_type.py",
3636
"directives_cast.py",
37+
"directives_deprecated.py",
3738
"directives_no_type_check.py",
3839
"directives_reveal_type.py",
3940
"directives_type_checking.py",
@@ -107,7 +108,6 @@
107108
"constructors_callable.py": 9,
108109
"dataclasses_final.py": 1,
109110
"dataclasses_slots.py": 2,
110-
"directives_deprecated.py": 1,
111111
"enums_members.py": 3,
112112
"exceptions_context_managers.py": 2,
113113
"generics_base_class.py": 1,

pyrefly/lib/alt/call.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,23 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
249249
context: Option<&dyn Fn() -> ErrorContext>,
250250
) -> CallTarget {
251251
match self.as_call_target(ty.clone()) {
252-
Some(target) => target,
252+
Some(target) => {
253+
let metadata = target.function_metadata();
254+
if let Some(m) = metadata
255+
&& m.flags.is_deprecated
256+
{
257+
self.error(
258+
errors,
259+
range,
260+
ErrorInfo::new(ErrorKind::Deprecated, context),
261+
format!(
262+
"`{}` is deprecated",
263+
m.kind.as_func_id().format(self.module().name())
264+
),
265+
);
266+
}
267+
target
268+
}
253269
None => {
254270
let expect_message = match call_style {
255271
CallStyle::Method(method) => {
@@ -566,19 +582,6 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
566582
ctor_targs: Option<&mut TArgs>,
567583
) -> Type {
568584
let metadata = call_target.function_metadata();
569-
if let Some(m) = metadata
570-
&& m.flags.is_deprecated
571-
{
572-
self.error(
573-
errors,
574-
range,
575-
ErrorInfo::new(ErrorKind::Deprecated, context),
576-
format!(
577-
"Call to deprecated function `{}`",
578-
m.kind.as_func_id().format(self.module().name())
579-
),
580-
);
581-
}
582585
// Does this call target correspond to a function whose keyword arguments we should save?
583586
let kw_metadata = {
584587
if let Some(m) = metadata

pyrefly/lib/alt/expr.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,34 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
215215
.into_ty()
216216
}
217217

218+
/// Check whether a type corresponds to a deprecated function or method, and if so, log a deprecation warning.
219+
pub fn check_for_deprecated_call(&self, ty: &Type, range: TextRange, errors: &ErrorCollector) {
220+
let deprecated_function = match ty {
221+
Type::Function(f) if f.metadata.flags.is_deprecated => {
222+
Some(f.metadata.kind.as_func_id().format(self.module().name()))
223+
}
224+
Type::BoundMethod(m) if m.func.metadata().flags.is_deprecated => Some(
225+
m.func
226+
.metadata()
227+
.kind
228+
.as_func_id()
229+
.format(self.module().name()),
230+
),
231+
Type::Overload(o) if o.metadata.flags.is_deprecated => {
232+
Some(o.metadata.kind.as_func_id().format(self.module().name()))
233+
}
234+
_ => None,
235+
};
236+
if let Some(deprecated_function) = deprecated_function {
237+
self.error(
238+
errors,
239+
range,
240+
ErrorInfo::Kind(ErrorKind::Deprecated),
241+
format!("`{}` is deprecated", deprecated_function),
242+
);
243+
}
244+
}
245+
218246
/// Like expr_infer_with_hint(), but returns a TypeInfo that includes narrowing information.
219247
pub fn expr_infer_type_info_with_hint(
220248
&self,
@@ -264,6 +292,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
264292
// at the end.
265293
_ => TypeInfo::of_ty(self.expr_infer_type_no_trace(x, hint, errors)),
266294
};
295+
// Check for deprecation
296+
self.check_for_deprecated_call(res.ty(), x.range(), errors);
267297
self.record_type_trace(x.range(), res.ty());
268298
res
269299
}

pyrefly/lib/test/calls.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,20 @@ testcase!(
9090
from warnings import deprecated
9191
@deprecated("function is deprecated")
9292
def old_function() -> None: ...
93-
old_function() # E: Call to deprecated function `old_function`
93+
old_function() # E: `old_function` is deprecated
94+
"#,
95+
);
96+
97+
testcase!(
98+
test_deprecated_function_reference,
99+
r#"
100+
from typing import Callable
101+
from warnings import deprecated
102+
@deprecated("function is deprecated")
103+
def old_function() -> None: ...
104+
105+
def take_callable(f: Callable) -> None: ...
106+
take_callable(old_function) # E: `old_function` is deprecated
94107
"#,
95108
);
96109

@@ -103,7 +116,7 @@ class C:
103116
def old_function(self) -> None: ...
104117
105118
c = C()
106-
c.old_function() # E: Call to deprecated function `C.old_function`
119+
c.old_function() # E: `C.old_function` is deprecated
107120
"#,
108121
);
109122

@@ -121,7 +134,7 @@ def f(x: str) -> str: ...
121134
def f(x: int | str) -> int | str:
122135
return x
123136
124-
f(0) # E: Call to deprecated function `f`
137+
f(0) # E: `f` is deprecated
125138
"#,
126139
);
127140

pyrefly/lib/test/imports.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ testcase!(
861861
r#"
862862
from foo import x # E: `x` is deprecated
863863
864-
x() # E: Call to deprecated function `foo.x`
864+
x() # E: `foo.x` is deprecated
865865
"#,
866866
);
867867

@@ -871,7 +871,7 @@ testcase!(
871871
r#"
872872
from foo import x as y # E: `x` is deprecated
873873
874-
y() # E: Call to deprecated function `foo.x`
874+
y() # E: `foo.x` is deprecated
875875
"#,
876876
);
877877

@@ -881,7 +881,7 @@ testcase!(
881881
r#"
882882
from foo import * # E: `x` is deprecated
883883
884-
x() # E: Call to deprecated function `foo.x`
884+
x() # E: `foo.x` is deprecated
885885
"#,
886886
);
887887

@@ -923,7 +923,7 @@ testcase!(
923923
r#"
924924
from foo import x # E: `x` is deprecated
925925
926-
x() # E: Call to deprecated function `foo.x`
926+
x() # E: `foo.x` is deprecated
927927
"#,
928928
);
929929

test/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ $ echo "x: str = 12" > $TMPDIR/shown1.py && \
2323
> echo "import shown1; y: int = shown1.x" > $TMPDIR/shown2.py && \
2424
> $PYREFLY check --python-version 3.13.0 $TMPDIR/shown2.py --check-all --output-format=min-text
2525
*/shown*.py:1:* (glob)
26+
WARN ast.pyi:1102:10-11: `Constant.n` is deprecated [deprecated]
27+
WARN ast.pyi:1102:10-18: `Constant.n` is deprecated [deprecated]
28+
WARN ast.pyi:1107:10-11: `Constant.s` is deprecated [deprecated]
29+
WARN ast.pyi:1107:10-18: `Constant.s` is deprecated [deprecated]
2630
WARN importlib/_bootstrap_external.pyi:1:40-41: `WindowsRegistryFinder` is deprecated [deprecated]
2731
*/shown*.py:1:* (glob)
2832
[1]

0 commit comments

Comments
 (0)