Skip to content

Commit

Permalink
[infer/py] initial support for multiple inheritance
Browse files Browse the repository at this point in the history
Summary: Adding support for multiple inheritance. We currently don't support calling `super()` in such classes. It will be implemented in another diff

Reviewed By: ngorogiannis

Differential Revision: D49410814

fbshipit-source-id: 32b8d215ec6c84962d31f9d942b15743b6dd9741
  • Loading branch information
Vincent Siles authored and facebook-github-bot committed Sep 26, 2023
1 parent 21183fe commit 183a1f7
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 44 deletions.
14 changes: 7 additions & 7 deletions infer/src/python/PyEnv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ end
(* TODO: try to merge this in a [globals]/[Symbol]. However be careful not to override the
original parent info since it might not be locally available when we executer [register_symbol]
after [register_class] *)
type class_info = {parent: Ident.t option}
type class_info = {parents: Ident.t list}

type label_info =
{label_name: string; ssa_parameters: T.Typ.t list; prelude: prelude option; processed: bool}
Expand Down Expand Up @@ -613,15 +613,15 @@ let lookup_method {shared= {signatures}} ~enclosing_class name =

let lookup_fields {shared= {fields}} class_name = T.TypeName.Map.find_opt class_name fields

let register_class ({shared} as env) class_name parent =
let register_class ({shared} as env) class_name parents =
PyDebug.p "[register_class] %s\n" class_name ;
( match parent with
| None ->
( match parents with
| [] ->
PyDebug.p "\n"
| Some parent ->
PyDebug.p " extending %a\n" Ident.pp parent ) ;
| _ :: _ ->
PyDebug.p " extending %a\n" (Pp.seq ~sep:", " Ident.pp) parents ) ;
let {classes} = shared in
let classes = SMap.add class_name {parent} classes in
let classes = SMap.add class_name {parents} classes in
let shared = {shared with classes} in
{env with shared}

Expand Down
9 changes: 5 additions & 4 deletions infer/src/python/PyEnv.mli
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ module Label : sig
(** Process a label [info] and turn it into Textual information *)
end

(** Class Level info. For now, only the parent info (if present) is tracked. We may track more
information, like being an abstract class, a dataclass, ... *)
type class_info = {parent: Ident.t option}
(** Class Level info. For now, only the parent info (if present) is tracked, supporting multiple
inheritance. We may track more information in the future, like being an abstract class, a
dataclass, ... *)
type class_info = {parents: Ident.t list}

val empty : Ident.t -> t

Expand Down Expand Up @@ -238,7 +239,7 @@ val lookup_fields : t -> T.TypeName.t -> PyCommon.signature option

(** Lookup the signature of a function / method *)

val register_class : t -> string -> Ident.t option -> t
val register_class : t -> string -> Ident.t list -> t
(** Register a class declaration (based on [LOAD_BUILD_CLASS]) *)

val get_declared_classes : t -> class_info PyCommon.SMap.t
Expand Down
76 changes: 43 additions & 33 deletions infer/src/python/PyTrans.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ module Error = struct
| LoadMapCells
| FunctionFlags of string * MakeFunctionFlags.t
| StaticCallImport of Ident.t
| LoadMultipleClasses of int
| SuperNotInFile of Ident.t * Ident.t
| SuperMultipleInheritance of Ident.t
| Closure of string
| CompareOp of Builtin.Compare.t
| Exception of string * DataStack.cell
Expand All @@ -42,12 +42,12 @@ module Error = struct
flags
| StaticCallImport id ->
F.fprintf fmt "Unsupported Import %a in static call" Ident.pp id
| LoadMultipleClasses n ->
F.fprintf fmt "Unsupported LOAD_BUILD_CLASS with arg = %d" n
| SuperNotInFile (id, parent) ->
F.fprintf fmt
"Unsupported `super` in class %a because its parent %a is not in the same file" Ident.pp
id Ident.pp parent
| SuperMultipleInheritance id ->
F.fprintf fmt "Unsupported call to `super` with multiple inheritance in %a" Ident.pp id
| Closure opname ->
F.fprintf fmt "[%s] Unsupported statement with closure" opname
| CompareOp op ->
Expand Down Expand Up @@ -76,6 +76,8 @@ module Error = struct
| EmptyStack of string * string
| BuiltinClassName of string * string * DataStack.cell
| BuiltinClassBody of string * DataStack.cell
| BuiltinClassInvalidParents of string
| BuiltinClassInvalid
| CallInvalid of string * DataStack.cell
| MakeFunctionInvalidCode of DataStack.cell
| MakeFunctionInvalidName of DataStack.cell
Expand Down Expand Up @@ -132,6 +134,10 @@ module Error = struct
DataStack.pp_cell cell
| BuiltinClassBody (opname, cell) ->
F.fprintf fmt "[%s] Expected code object but got %a" opname DataStack.pp_cell cell
| BuiltinClassInvalidParents name ->
F.fprintf fmt "Failure to load parent classes of '%s" name
| BuiltinClassInvalid ->
F.pp_print_string fmt "Failure to load class: not enough information can be found"
| CallInvalid (opname, cell) ->
F.fprintf fmt "[%s] invalid object to call: %a" opname DataStack.pp_cell cell
| MakeFunctionInvalidCode cell ->
Expand Down Expand Up @@ -723,7 +729,7 @@ module FUNCTION = struct
Ok (env, None)


let builtin_build_class env code opname name_cell code_cell parent_cell =
let builtin_build_class env code opname name_cell code_cell parent_cells =
let open IResult.Let_syntax in
let as_name kind cell =
match DataStack.as_name code cell with
Expand All @@ -733,15 +739,19 @@ module FUNCTION = struct
Error (L.ExternalError, Error.BuiltinClassName (opname, kind, cell))
in
let* code_name = as_name "base" name_cell in
let parent = Option.bind ~f:(DataStack.as_id code) parent_cell in
let* parents =
let ids = List.map ~f:(DataStack.as_id code) parent_cells in
let ids = Option.all ids in
Result.of_option ids ~error:(L.InternalError, Error.BuiltinClassInvalidParents code_name)
in
let* code =
match DataStack.as_code code code_cell with
| Some code ->
Ok code
| None ->
Error (L.ExternalError, Error.BuiltinClassBody (opname, code_cell))
in
let env = Env.register_class env code_name parent in
let env = Env.register_class env code_name parents in
let env = Env.push env (DataStack.Code {fun_or_class= false; code_name; code}) in
Ok env

Expand Down Expand Up @@ -774,13 +784,13 @@ module FUNCTION = struct
| BuiltinBuildClass -> (
match cells with
| [code_cell; name_cell] ->
let* env = builtin_build_class env code opname name_cell code_cell None in
let* env = builtin_build_class env code opname name_cell code_cell [] in
Ok (env, None)
| [code_cell; name_cell; parent_cell] ->
let* env = builtin_build_class env code opname name_cell code_cell (Some parent_cell) in
| code_cell :: name_cell :: parent_cells ->
let* env = builtin_build_class env code opname name_cell code_cell parent_cells in
Ok (env, None)
| _ ->
Error (L.InternalError, Error.TODO (LoadMultipleClasses arg)) )
Error (L.InternalError, Error.BuiltinClassInvalid) )
| Path id ->
static_call env code id cells
| ImportCall {id; loc} ->
Expand Down Expand Up @@ -929,24 +939,26 @@ module METHOD = struct
let class_info = SMap.find_opt class_name classes in
let params = Env.get_params env in
let is_static = Env.is_static env in
let* parent_info =
let* parent =
match class_info with
| None ->
Error (L.ExternalError, Error.SuperNotInClass current_module)
| Some {parent} ->
Ok parent
| Some {parents} -> (
match parents with
| [] ->
Error (L.ExternalError, Error.SuperNoParent current_module)
| [parent] ->
Ok parent
| _ ->
Error (L.InternalError, Error.TODO (SuperMultipleInheritance current_module)) )
in
let* parent_name =
match parent_info with
let key = Symbol.Global parent in
match Env.lookup_symbol env key with
| None ->
Error (L.ExternalError, Error.SuperNoParent current_module)
| Some parent -> (
let key = Symbol.Global parent in
match Env.lookup_symbol env key with
| None ->
Error (L.InternalError, Error.TODO (SuperNotInFile (current_module, parent)))
| Some {Symbol.id} ->
Ok id )
Error (L.InternalError, Error.TODO (SuperNotInFile (current_module, parent)))
| Some {Symbol.id} ->
Ok id
in
let* self =
if is_static then Error (L.ExternalError, Error.SuperInStatic current_module)
Expand Down Expand Up @@ -2630,9 +2642,9 @@ let constructor_stubs ~kind loc class_name =

(** Process the special function generated by the Python compiler to create a class instances. This
is where we can see some of the type annotations for fields, and method definitions. *)
let rec class_declaration env module_name ({FFI.Code.instructions; co_name} as code) loc parent =
let rec class_declaration env module_name ({FFI.Code.instructions; co_name} as code) loc parents =
let open IResult.Let_syntax in
Debug.p "[class declaration] %s (%a)\n" co_name (Pp.option Ident.pp) parent ;
Debug.p "[class declaration] %s (%a)\n" co_name (Pp.seq ~sep:", " Ident.pp) parents ;
(* TODO:
- use annotations to declare class member types
- pass in [env] to PyClassDecl when we'll support decorators
Expand Down Expand Up @@ -2681,10 +2693,8 @@ let rec class_declaration env module_name ({FFI.Code.instructions; co_name} as c
in
let* env, decls = to_proc_descs env class_name (Array.of_list codes) in
let supers, static_supers =
match parent with
| None ->
([], [])
| Some parent ->
(* TODO: check if/how ordering of parent class matters. Atm I keep the same order. *)
List.fold_right ~init:([], []) parents ~f:(fun parent (supers, static_supers) ->
let parent_id =
let key = Symbol.Global parent in
match Env.lookup_symbol env key with Some {Symbol.id} -> Some id | _ -> None
Expand All @@ -2699,9 +2709,9 @@ let rec class_declaration env module_name ({FFI.Code.instructions; co_name} as c
since compilation was happy *)
Option.value parent_id ~default:parent
in
let supers = [Ident.to_type_name ~static:false parent] in
let static_supers = [Ident.to_type_name ~static:true parent] in
(supers, static_supers)
let supers = Ident.to_type_name ~static:false parent :: supers in
let static_supers = Ident.to_type_name ~static:true parent :: static_supers in
(supers, static_supers) )
in
let t = T.Module.Struct {name= class_type_name; supers; fields; attributes= []} in
let companion =
Expand Down Expand Up @@ -2749,8 +2759,8 @@ and to_proc_descs env enclosing_class_id codes =
(* TODO: supported nested classes *)
let classes = Env.get_declared_classes env in
match SMap.find_opt co_name classes with
| Some {parent} ->
let* env, new_decls = class_declaration env enclosing_class_id code loc parent in
| Some {parents} ->
let* env, new_decls = class_declaration env enclosing_class_id code loc parents in
Ok (env, new_decls @ decls)
| None ->
let* env, decl = to_proc_desc env loc enclosing_class_id (Some co_name) code in
Expand Down
77 changes: 77 additions & 0 deletions infer/src/python/unit/PyTransTest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,83 @@ def g(c: C) -> None:
declare $builtins.python_float(float) : *PyFloat
declare $builtins.python_int(int) : *PyInt |}]
let%expect_test _ =
let source =
{|
class A:
pass
class B:
pass
class C(A, B):
pass
|}
in
test source ;
[%expect
{|
.source_language = "python"
define dummy.$toplevel() : *PyNone {
#b0:
n0 = $builtins.python_class("dummy::A")
n1 = $builtins.python_class("dummy::B")
n2 = $builtins.python_class("dummy::C")
ret null
}
declare dummy::A(...) : *dummy::A
declare dummy::A.__init__(...) : *PyNone
global dummy::A$static: *PyObject
type .static dummy::A$static = {}
type dummy::A = {}
declare dummy::B(...) : *dummy::B
declare dummy::B.__init__(...) : *PyNone
global dummy::B$static: *PyObject
type .static dummy::B$static = {}
type dummy::B = {}
declare dummy::C(...) : *dummy::C
declare dummy::C.__init__(...) : *PyNone
global dummy::C$static: *PyObject
type .static dummy::C$static extends dummy::A$static, dummy::B$static = {
}
type dummy::C extends dummy::A, dummy::B = {}
global $python_implicit_names::__name__: *PyString
global $python_implicit_names::__file__: *PyString
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 |}]
end )
Expand Down

0 comments on commit 183a1f7

Please sign in to comment.