Skip to content

Commit

Permalink
[inferpython][specialization types] do not block type propagation bec…
Browse files Browse the repository at this point in the history
…ause of decorators

Summary: decorators on classes and functions should not stop the inference of specialization types.

Reviewed By: ngorogiannis

Differential Revision:
D67187567

Privacy Context Container: L1208441

fbshipit-source-id: f0674491260b5d94011496520bb68fc493308cd4
  • Loading branch information
davidpichardie authored and facebook-github-bot committed Dec 13, 2024
1 parent a632165 commit 45e93a9
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 13 deletions.
20 changes: 19 additions & 1 deletion infer/src/python/PyIR2Textual.ml
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ let builtin_name builtin =

let str_py_store_name = "py_store_name"

let str_py_call = "py_call"

let of_stmt loc stmt : Textual.Instr.t =
match (stmt : Stmt.t) with
| Let {lhs; rhs} ->
Expand Down Expand Up @@ -408,7 +410,7 @@ let of_stmt loc stmt : Textual.Instr.t =
; loc }
| Call {lhs; exp; args; arg_names} ->
let args = of_exp exp :: of_exp arg_names :: List.map ~f:of_exp args in
Let {id= Some (mk_ident lhs); exp= call_builtin "py_call" args; loc}
Let {id= Some (mk_ident lhs); exp= call_builtin str_py_call args; loc}
| CallMethod {lhs; name; self_if_needed; args; arg_names} ->
Let
{ id= Some (mk_ident lhs)
Expand Down Expand Up @@ -712,6 +714,8 @@ let py_import_from = builtin_qual_proc_name str_py_import_from

let py_build_class = builtin_qual_proc_name str_py_build_class

let py_call = builtin_qual_proc_name str_py_call

let py_store_name = builtin_qual_proc_name str_py_store_name

let py_make_function = builtin_qual_proc_name str_py_make_function
Expand Down Expand Up @@ -757,6 +761,20 @@ let gen_type module_name ~allow_classes name node =
let name = DefaultType.get_string_constant ident_str_const acc |> Option.value_exn in
let acc = DefaultType.add_class_body ident name acc in
find_next_declaration acc instrs
| Instr.Let {id= Some ident; exp= Call {proc; args= [Var _id_decorator; _; Var id_fun_ptr]}}
:: instrs
when QualifiedProcName.equal py_call proc && DefaultType.is_fun_ptr id_fun_ptr acc ->
(* note: we could filter the decorator in id_decorator if needed *)
let typ = DefaultType.get_fun_ptr id_fun_ptr acc |> Option.value_exn in
let acc = DefaultType.add_fun_ptr ident typ acc in
find_next_declaration acc instrs
| Instr.Let {id= Some ident; exp= Call {proc; args= [Var _id_decorator; _; Var id_class]}}
:: instrs
when QualifiedProcName.equal py_call proc && DefaultType.is_class_body id_class acc ->
(* note: we could filter the decorator in id_decorator if needed *)
let name = DefaultType.get_class_body id_class acc |> Option.value_exn in
let acc = DefaultType.add_class_body ident name acc in
find_next_declaration acc instrs
| Instr.Let {exp= Call {proc; args= [Const (Str target); _; _; Var ident]}} :: instrs
when QualifiedProcName.equal py_store_name proc && DefaultType.is_fun_ptr ident acc ->
let typ = DefaultType.get_fun_ptr ident acc |> Option.value_exn in
Expand Down
24 changes: 18 additions & 6 deletions infer/tests/codetoanalyze/python/pulse/async_import_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,32 @@

import asyncio
import async_utils as utils
from async_utils import await_it as async_await, dont_await_it as async_dont_await

from async_utils import (
await_it as async_await,
dont_await_it as async_dont_await,
sleep,
C
)

async def with_import_bad():
await utils.dont_await_it(asyncio.sleep(1))
await utils.dont_await_it(sleep())


async def with_import_ok():
await utils.await_it(asyncio.sleep(1))
await utils.await_it(sleep())


async def with_from_import_bad():
await async_dont_await(asyncio.sleep(1))
await async_dont_await(sleep())


async def with_from_import_ok():
await async_await(asyncio.sleep(1))
await async_await(sleep())


async def with_imported_class_bad():
await async_dont_await(C.sleep())


async def with_imported_class_ok():
await async_await(C.sleep())
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,31 @@
# LICENSE file in the root directory of this source tree.

import asyncio
from dir1.testmod import await_it as await_it1, dont_await_it as dont_await_it1
from dir1.dir3.testmod import await_it as await_it3, dont_await_it as dont_await_it3
from dir1.dir4.testmod import await_it as await_it4, dont_await_it as dont_await_it4
from dir2.testmod import await_it as await_it2, dont_await_it as dont_await_it2
from dir2.dir5.testmod import await_it as await_it5, dont_await_it as dont_await_it5
from dir2.dir6.testmod import await_it as await_it6, dont_await_it as dont_await_it6
from dir1.testmod import (
await_it as await_it1,
dont_await_it as dont_await_it1,
)
from dir1.dir3.testmod import (
await_it as await_it3,
dont_await_it as dont_await_it3,
C
)
from dir1.dir4.testmod import (
await_it as await_it4,
dont_await_it as dont_await_it4,
)
from dir2.testmod import (
await_it as await_it2,
dont_await_it as dont_await_it2,
)
from dir2.dir5.testmod import (
await_it as await_it5,
dont_await_it as dont_await_it5,
)
from dir2.dir6.testmod import (
await_it as await_it6,
dont_await_it as dont_await_it6,
)


async def bad1():
Expand All @@ -36,6 +55,14 @@ async def ok3():
await await_it3(asyncio.sleep(1))


async def from_class_bad3():
await dont_await_it3(C.async_fun())


async def from_class_ok3():
await await_it3(C.async_fun())


async def bad4():
await dont_await_it4(asyncio.sleep(1))

Expand Down
13 changes: 13 additions & 0 deletions infer/tests/codetoanalyze/python/pulse/async_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,21 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.


async def sleep():
pass


async def await_it(arg):
await arg


async def dont_await_it(arg):
pass

@final
class C:

@staticmethod
async def sleep():
pass
7 changes: 7 additions & 0 deletions infer/tests/codetoanalyze/python/pulse/dir1/dir3/testmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ async def await_it(arg):

async def dont_await_it(arg):
pass

@final
class C:

@staticmethod
async def async_fun():
pass
2 changes: 2 additions & 0 deletions infer/tests/codetoanalyze/python/pulse/issues.exp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
async_global.py, async_global.__module_body__, 24, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,parameter `globals` of async_global.__module_body__,assigned,when calling `closure:async_global:0.call` here,parameter `__this` of closure:async_global:0.call,when calling `async_global.set_unwaited_global` here,allocated by async call here,awaitable becomes unreachable here]
async_import_simple.py, async_import_simple.with_import_bad, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_simple.py, async_import_simple.with_from_import_bad, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_simple.py, async_import_simple.with_imported_class_bad, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.bad1, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.bad2, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.bad3, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.from_class_bad3, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.bad4, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.bad5, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
async_import_with_package.py, async_import_with_package.bad6, 1, PULSE_UNAWAITED_AWAITABLE, no_bucket, ERROR, [allocation part of the trace starts here,allocated by async call here,awaitable becomes unreachable here]
Expand Down

0 comments on commit 45e93a9

Please sign in to comment.