Skip to content

Commit

Permalink
[infer/py][1/3] Introduce support for function with default arguments
Browse files Browse the repository at this point in the history
Summary:
Introducing a new kind of cell to keep a list on the stack. This will be used in the next diff to generate specialised versions of functions with default arguments inlined

A known issue is around primitive types vs objects as default arguments. The current way we store them won't work for objects.

Reviewed By: ngorogiannis

Differential Revision: D49494735

fbshipit-source-id: b03fe58b820913db615c002979278b0c1dd6edb4
  • Loading branch information
Vincent Siles authored and facebook-github-bot committed Sep 29, 2023
1 parent dc92f0e commit 442b2ca
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 6 deletions.
7 changes: 7 additions & 0 deletions infer/src/python/PyCommon.ml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ let get_string = function
None


let get_tuple_as_list = function
| T.Exp.Call {proc; args; kind= NonVirtual} when T.equal_qualified_procname proc python_tuple ->
Some args
| _ ->
None


let mk_bytes (s : bytes) =
let proc = python_bytes in
let s = Bytes.to_string s in
Expand Down
3 changes: 3 additions & 0 deletions infer/src/python/PyCommon.mli
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ val mk_string : string -> Textual.Exp.t
val get_string : Textual.Exp.t -> string option
(** Helper to get back a string built with [mk_string] *)

val get_tuple_as_list : Textual.Exp.t -> Textual.Exp.t list option
(** Helper to get back the inner elements of a [python_tuple] *)

val mk_bytes : bytes -> Textual.Exp.t
(** Helper function to define typed Textual expression for literal bytes. *)

Expand Down
6 changes: 6 additions & 0 deletions infer/src/python/PyEnv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ module DataStack = struct
| VarName of int
| Temp of T.Ident.t
| Code of {fun_or_class: bool; code_name: string; code: FFI.Code.t}
| List of (Builtin.collection * T.Exp.t list)
| Map of (T.Exp.t * cell) list
| BuiltinBuildClass
| Import of {import_path: Ident.t; symbols: string list}
Expand All @@ -104,6 +105,8 @@ module DataStack = struct
F.fprintf fmt "Temp(%a)" T.Ident.pp id
| Code {fun_or_class; code_name} ->
F.fprintf fmt "Code(%s, %s)" (if fun_or_class then "fun" else "class") code_name
| List _ ->
F.pp_print_string fmt "List"
| Map _ ->
F.pp_print_string fmt "Map"
| BuiltinBuildClass ->
Expand Down Expand Up @@ -141,6 +144,7 @@ module DataStack = struct
| Name _
| Temp _
| VarName _
| List _
| Map _
| BuiltinBuildClass
| Import _
Expand All @@ -164,6 +168,7 @@ module DataStack = struct
| Path _
| Code _
| Temp _
| List _
| Map _
| BuiltinBuildClass
| Import _
Expand All @@ -188,6 +193,7 @@ module DataStack = struct
Some id
| Code _
| Temp _
| List _
| Map _
| BuiltinBuildClass
| Import _
Expand Down
5 changes: 3 additions & 2 deletions infer/src/python/PyEnv.mli
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ module DataStack : sig
| Temp of T.Ident.t (** SSA variable *)
| Code of {fun_or_class: bool; code_name: string; code: FFI.Code.t}
(** [code] Python object with its name. It can be a function, class, closure, ... *)
| Map of (T.Exp.t * cell) list
(** Light encoding of raw Python tuples/dicts. Only used for type annotations at the moment. *)
| List of (PyBuiltin.collection * T.Exp.t list)
(** Light encoding of raw Python tuples/lists. *)
| Map of (T.Exp.t * cell) list (** Light encoding of raw Python maps/dicts. *)
| BuiltinBuildClass (** see Python's [LOAD_BUILD_CLASS] *)
| Import of {import_path: Ident.t; symbols: string list}
(** imported module path, with optional name of symbols *)
Expand Down
47 changes: 43 additions & 4 deletions infer/src/python/PyTrans.ml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module Error = struct
| CallKwInvalidFunction of DataStack.cell
| RaiseException of int
| RaiseExceptionSource of DataStack.cell
| MakeFunctionDefault of DataStack.cell
| MakeFunctionSignature of DataStack.cell

let pp_todo fmt = function
Expand Down Expand Up @@ -73,6 +74,8 @@ module Error = struct
F.fprintf fmt "Unsupported RAISE_VARARGS mode %d" n
| RaiseExceptionSource cell ->
F.fprintf fmt "Unsupported source of exception: %a" DataStack.pp_cell cell
| MakeFunctionDefault cell ->
F.fprintf fmt "Unsupported function defaults: %a" DataStack.pp_cell cell
| MakeFunctionSignature cell ->
F.fprintf fmt "Unsupported type annotation: %a" DataStack.pp_cell cell

Expand Down Expand Up @@ -460,6 +463,10 @@ let rec load_cell env ({FFI.Code.co_consts; co_names; co_varnames} as code) cell
let env, id, typ = Env.mk_builtin_call env (Builtin.PythonBuild Map) items in
let env = Env.push env (DataStack.Temp id) in
Ok (env, T.Exp.Var id, default_info typ)
| List (collection, items) ->
let env, id, typ = Env.mk_builtin_call env (Builtin.PythonBuild collection) items in
let env = Env.push env (DataStack.Temp id) in
Ok (env, T.Exp.Var id, default_info typ)
| Path id ->
if
(* TODO: this is incomplete. If something is imported, we can' really know if it is a
Expand Down Expand Up @@ -673,10 +680,9 @@ end

let check_flags opname flags =
let open MakeFunctionFlags in
let has_tuple_of_defaults = mem flags DefaultValues in
let has_dict_of_defaults = mem flags DictDefaultValues in
let has_closure = mem flags Closure in
let unsupported = has_tuple_of_defaults || has_dict_of_defaults || has_closure in
let unsupported = has_dict_of_defaults || has_closure in
if unsupported then Error (L.InternalError, Error.TODO (FunctionFlags (opname, flags))) else Ok ()


Expand Down Expand Up @@ -821,6 +827,7 @@ module FUNCTION = struct
| WithContext _
| VarName _
| Const _
| List _
| Map _ ->
(* TODO: should be user error, but for now we might trigger it because we don't cover
enough constructions of the language, so it's an internal error for now *)
Expand Down Expand Up @@ -878,6 +885,24 @@ module FUNCTION = struct
annotations


let unpack_defaults env code defaults =
let open IResult.Let_syntax in
match (defaults : DataStack.cell) with
| Const _ ->
let* env, tuple, _typ = load_cell env code defaults in
let args = PyCommon.get_tuple_as_list tuple in
let* args =
Result.of_option
~error:(L.InternalError, Error.TODO (MakeFunctionDefault defaults))
args
in
Ok (env, args)
| List (_, args) ->
Ok (env, args)
| _ ->
Error (L.InternalError, Error.TODO (MakeFunctionDefault defaults))


(** {v MAKE_FUNCTION(flags) v}
Pushes a new function object on the stack. From bottom to top, the consumed stack must
Expand Down Expand Up @@ -912,6 +937,15 @@ module FUNCTION = struct
let* annotations =
Option.value_map ~default:(Ok []) ~f:(unpack_annotations env code) annotations
in
let* env =
if MakeFunctionFlags.mem flags DefaultValues then (
let* env, cell = pop_datastack opname env in
let* env, _defaults = unpack_defaults env code cell in
L.debug Capture Quiet
"[MAKE_FUNCTION] TODO generate overriding functions with default args inlined@\n" ;
Ok env )
else Ok env
in
let* code =
match DataStack.as_code code body with
| None ->
Expand All @@ -925,6 +959,7 @@ module FUNCTION = struct
| Name _
| Temp _
| Code _
| List _
| Map _
| BuiltinBuildClass
| Import _
Expand Down Expand Up @@ -1059,6 +1094,7 @@ module FUNCTION = struct
| Code _
| VarName _
| Const _
| List _
| Map _
| Import _
| StaticCall _
Expand Down Expand Up @@ -1226,6 +1262,7 @@ module METHOD = struct
| Temp _
| Code _
| Map _
| List _
| BuiltinBuildClass
| Import _
| NoException
Expand Down Expand Up @@ -1566,8 +1603,7 @@ module BUILD = struct
Debug.p "[%s] count = %d\n" opname count ;
let* env, items = pop_n_datastack opname env count in
let* env, items = cells_to_textual env code items in
let env, id, _typ = Env.mk_builtin_call env (Builtin.PythonBuild collection) items in
let env = Env.push env (DataStack.Temp id) in
let env = Env.push env (DataStack.List (collection, items)) in
Ok (env, None)
end
end
Expand Down Expand Up @@ -2900,6 +2936,9 @@ let rec class_declaration env module_name ({FFI.Code.instructions; co_name} as c
~init:(Ok (env, codes))
~f:(fun acc {PyCommon.code; signature; flags; is_static; is_abstract} ->
let* env, codes = acc in
(* TODO: Fix class method parsing to deal with default parameters.
It will require a big rewrite of the whole thing, so
postponing to a later diff *)
let* () = check_flags opname flags in
let env =
match FFI.Constant.as_code code with
Expand Down
84 changes: 84 additions & 0 deletions infer/src/python/unit/PyTransTest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3375,3 +3375,87 @@ raise C
declare $builtins.python_int(int) : *PyInt |}]
end )
let%test_module "default_arguments" =
( module struct
let%expect_test _ =
let source =
{|
class C:
pass
c = C()
def f(x: int, y=c, z=C()):
pass
def g(x, y=1, z=2):
pass
|}
in
test source ;
[%expect
{|
.source_language = "python"
define dummy.$toplevel() : *PyNone {
#b0:
n0 = $builtins.python_class("dummy::C")
n1 = dummy::C()
store &dummy::c <- n1:*dummy::C
n2 = dummy::C()
n3:*dummy::C = load &dummy::c
n4 = $builtins.python_code("dummy.f")
n5 = $builtins.python_code("dummy.g")
ret null
}
declare dummy::C(...) : *dummy::C
declare dummy::C.__init__(...) : *PyNone
global dummy::C$static: *PyObject
type .static dummy::C$static = {}
type dummy::C = {}
define dummy.f(x: *PyInt, y: *PyObject, z: *PyObject) : *PyObject {
#b0:
ret null
}
define dummy.g(x: *PyObject, y: *PyObject, z: *PyObject) : *PyObject {
#b0:
ret null
}
global dummy::c: *PyObject
global $python_implicit_names::__name__: *PyString
global $python_implicit_names::__file__: *PyString
declare $builtins.python_code(*String) : *PyCode
declare $builtins.python_class(*String) : *PyClass
declare $builtins.python_tuple(...) : *PyObject
declare $builtins.python_bytes(*Bytes) : *PyBytes
declare $builtins.python_string(*String) : *PyString
declare $builtins.python_bool(int) : *PyBool
declare $builtins.python_float(float) : *PyFloat
declare $builtins.python_int(int) : *PyInt
[MAKE_FUNCTION] TODO generate overriding functions with default args inlined
[MAKE_FUNCTION] TODO generate overriding functions with default args inlined |}]
end )

0 comments on commit 442b2ca

Please sign in to comment.