source/analysis/scope.ml (739 lines of code) (raw):

(* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) open Core open Ast open Pyre module Binding = struct module Kind = struct module Star = struct type t = | Once | Twice [@@deriving sexp, compare, hash] end module Import = struct type t = | From of Reference.t | Module [@@deriving sexp, compare, hash] end type t = | AssignTarget of Expression.t option | ClassName | ComprehensionTarget | DefineName of Statement.Define.Signature.t | ExceptTarget of Expression.t option | ForTarget | ImportName of Import.t | MatchTarget | ParameterName of { index: int; annotation: Expression.t option; star: Star.t option; } | WalrusTarget | WithTarget [@@deriving sexp, compare, hash] end type t = { kind: Kind.t; name: Identifier.t; location: Location.t; } [@@deriving sexp, compare, hash] let name { name; _ } = name let rec of_unannotated_target ~kind sofar { Node.value = target; location } = let open Expression in match target with | Expression.Name (Name.Identifier name) -> { name; kind; location } :: sofar | Expression.Starred (Starred.Once element | Starred.Twice element) -> of_unannotated_target ~kind sofar element | Tuple elements | List elements -> (* Tuple or list cannot be annotated. *) List.fold elements ~init:sofar ~f:(of_unannotated_target ~kind) | _ -> sofar let rec of_expression sofar { Node.value = expression; _ } = let open Expression in match expression with | Expression.WalrusOperator { WalrusOperator.target; value } -> let sofar = of_expression sofar value in of_unannotated_target ~kind:Kind.WalrusTarget sofar target (* Boilerplates to make sure all expressions are visited. *) | Expression.Await expression | Expression.Name (Name.Attribute { Name.Attribute.base = expression; _ }) | Expression.UnaryOperator { UnaryOperator.operand = expression; _ } | Expression.Starred (Starred.Once expression | Starred.Twice expression) | Expression.Yield (Some expression) -> of_expression sofar expression | Expression.YieldFrom expression -> of_expression sofar expression | Expression.BooleanOperator { BooleanOperator.left; right; _ } | Expression.ComparisonOperator { ComparisonOperator.left; right; _ } -> let sofar = of_expression sofar left in of_expression sofar right | Expression.Call { Call.callee; arguments } -> let sofar = List.fold arguments ~init:sofar ~f:(fun sofar { Call.Argument.value; _ } -> of_expression sofar value) in of_expression sofar callee | Expression.Dictionary { Dictionary.entries; keywords } -> let sofar = List.fold entries ~init:sofar ~f:of_dictionary_entry in List.fold keywords ~init:sofar ~f:of_expression | Expression.DictionaryComprehension comprehension -> of_comprehension sofar comprehension ~of_element:of_dictionary_entry | Expression.Generator comprehension | Expression.ListComprehension comprehension | Expression.SetComprehension comprehension -> of_comprehension sofar comprehension ~of_element:of_expression | Expression.List elements | Expression.Set elements | Expression.Tuple elements -> List.fold elements ~init:sofar ~f:of_expression | Expression.Ternary { Ternary.target; test; alternative } -> let sofar = of_expression sofar target in let sofar = of_expression sofar test in of_expression sofar alternative | Expression.FormatString substrings -> let of_substring sofar = function | Substring.(Literal _) -> sofar | Substring.Format expression -> of_expression sofar expression in List.fold substrings ~init:sofar ~f:of_substring | Constant _ | Lambda _ | Name (Name.Identifier _) | Yield None -> sofar and of_dictionary_entry sofar { Expression.Dictionary.Entry.key; value } = let sofar = of_expression sofar key in of_expression sofar value and of_comprehension : 'a. of_element:(t list -> 'a -> t list) -> t list -> 'a Expression.Comprehension.t -> t list = fun ~of_element sofar { Expression.Comprehension.element; generators } -> let of_generator sofar { Expression.Comprehension.Generator.conditions; _ } = (* PEP-572 forbids assignment expressions in the iterator. We don't have to recurse there. *) List.fold conditions ~init:sofar ~f:of_expression in (* PEP-572 requires that assignment expressions in a comprehension bind the target in the containing scope *) let sofar = List.fold generators ~init:sofar ~f:of_generator in of_element sofar element let rec of_statement sofar { Node.value = statement; location } = let of_optional ~f sofar = Option.value_map ~default:sofar ~f:(f sofar) in let of_optional_expression sofar = of_optional ~f:of_expression sofar in let open Statement in match statement with | Statement.Assert { test; message; _ } -> let sofar = of_expression sofar test in of_optional_expression sofar message | Statement.Assign { Assign.target = { Node.value = Expression.Expression.Name (Expression.Name.Identifier name); location }; annotation = Some annotation; value; _; } -> let sofar = of_expression sofar value in { name; kind = Kind.AssignTarget (Some annotation); location } :: sofar | Statement.Assign { Assign.target; value; _ } -> let sofar = of_expression sofar value in of_unannotated_target ~kind:(Kind.AssignTarget None) sofar target | Statement.Class { Class.name; base_arguments; decorators; _ } -> let sofar = List.fold ~init:sofar ~f:of_expression decorators in let sofar = List.fold base_arguments ~init:sofar ~f:(fun sofar { Expression.Call.Argument.value; _ } -> of_expression sofar value) in { kind = Kind.ClassName; name = Reference.show name; location } :: sofar | Statement.Define { Define.signature = { name; decorators; parameters; _ } as signature; _ } -> let sofar = List.fold ~init:sofar ~f:of_expression decorators in let sofar = List.fold parameters ~init:sofar ~f:(fun sofar { Node.value = { Expression.Parameter.value; _ }; _ } -> of_optional_expression sofar value) in { kind = Kind.DefineName signature; name = Reference.show name; location } :: sofar | Statement.Expression expression -> of_expression sofar expression | Statement.For { For.target; iterator; body; orelse; _ } -> let sofar = of_unannotated_target ~kind:Kind.ForTarget sofar target in let sofar = of_expression sofar iterator in let sofar = of_statements sofar body in of_statements sofar orelse | Statement.If { If.test; body; orelse } -> let sofar = of_expression sofar test in let sofar = of_statements sofar body in of_statements sofar orelse | Statement.Import { Import.imports; from } -> let binding_of_import sofar { Node.value = { Import.alias; name }; location } = let import_status = match from with | Some from -> Kind.Import.From from | None -> Kind.Import.Module in match alias with | Some alias -> { kind = Kind.ImportName import_status; name = alias; location } :: sofar | None -> ( match from with | Some _ -> (* `name` must be a simple name *) { kind = Kind.ImportName import_status; name = Reference.show name; location } :: sofar | None -> (* `import a.b` actually binds name a *) let name = Reference.as_list name |> List.hd_exn in { kind = Kind.ImportName import_status; name; location } :: sofar) in List.fold imports ~init:sofar ~f:binding_of_import | Match { Match.subject; cases } -> let of_match_target sofar { Node.value = name; location } = { name; kind = Kind.MatchTarget; location } :: sofar in let rec of_pattern sofar { Node.value = pattern; location } = match pattern with | Match.Pattern.MatchAs { pattern; name } -> let sofar = of_optional ~f:of_pattern sofar pattern in name |> Node.create ~location |> of_match_target sofar | MatchClass { patterns; keyword_patterns; _ } -> let sofar = List.fold ~init:sofar ~f:of_pattern patterns in List.fold ~init:sofar ~f:of_pattern keyword_patterns | MatchMapping { patterns; rest; _ } -> let sofar = List.fold ~init:sofar ~f:of_pattern patterns in rest >>| Node.create ~location |> of_optional ~f:of_match_target sofar | MatchOr patterns | MatchSequence patterns -> List.fold ~init:sofar ~f:of_pattern patterns | MatchStar maybe_name -> maybe_name >>| Node.create ~location |> of_optional ~f:of_match_target sofar | MatchValue value -> of_expression sofar value | MatchSingleton _ | MatchWildcard -> sofar in let of_case sofar { Match.Case.pattern; guard; body } = let sofar = of_pattern sofar pattern in let sofar = of_optional_expression sofar guard in of_statements sofar body in let sofar = of_expression sofar subject in List.fold ~init:sofar ~f:of_case cases | Statement.Raise { Raise.expression; from } -> let sofar = of_optional_expression sofar expression in of_optional_expression sofar from | Statement.Return { Return.expression; _ } -> of_optional_expression sofar expression | Statement.Try { Try.body; handlers; orelse; finally } -> let bindings_of_handler sofar { Try.Handler.name; kind; body } = let sofar = match name with | None -> sofar | Some name -> (* TODO: Track the location of handler name. *) { kind = Kind.ExceptTarget kind; name; location } :: sofar in of_statements sofar body in let sofar = of_statements sofar body in let sofar = List.fold handlers ~init:sofar ~f:bindings_of_handler in let sofar = of_statements sofar orelse in of_statements sofar finally | Statement.While { While.test; body; orelse } -> let sofar = of_expression sofar test in let sofar = of_statements sofar body in of_statements sofar orelse | Statement.With { With.items; body; _ } -> let bindings_of_item sofar (expression, alias) = let sofar = of_expression sofar expression in match alias with | None -> sofar | Some target -> of_unannotated_target ~kind:Kind.WithTarget sofar target in let sofar = List.fold items ~init:sofar ~f:bindings_of_item in of_statements sofar body | Break | Continue | Delete _ | Global _ | Nonlocal _ | Pass -> sofar and of_statements sofar statements = List.fold statements ~init:sofar ~f:of_statement let of_parameters sofar parameters = let of_parameter index sofar { Node.value = { Expression.Parameter.name; annotation; _ }; location } = let star, name = let star, name = Identifier.split_star name in let star = match star with | "**" -> Some Kind.Star.Twice | "*" -> Some Kind.Star.Once | _ -> None in star, name in { kind = Kind.ParameterName { index; star; annotation }; name; location } :: sofar in List.foldi parameters ~init:sofar ~f:of_parameter let of_generators sofar generators = let of_generator sofar { Expression.Comprehension.Generator.target; _ } = of_unannotated_target ~kind:Kind.ComprehensionTarget sofar target in List.fold generators ~init:sofar ~f:of_generator end let rec globals_of_statement sofar { Node.value; _ } = let open Statement in match value with | Statement.Global globals -> List.rev_append globals sofar | For { For.body; orelse; _ } | If { If.body; orelse; _ } | While { While.body; orelse; _ } -> let sofar = globals_of_statements sofar body in globals_of_statements sofar orelse | Match { Match.cases; _ } -> List.fold cases ~init:sofar ~f:(fun sofar { Match.Case.body; _ } -> globals_of_statements sofar body) | Try { Try.body; handlers; orelse; finally } -> let sofar = globals_of_statements sofar body in let sofar = List.fold handlers ~init:sofar ~f:(fun sofar { Try.Handler.body; _ } -> globals_of_statements sofar body) in let sofar = globals_of_statements sofar orelse in globals_of_statements sofar finally | With { With.body; _ } -> globals_of_statements sofar body | Assign _ | Assert _ | Break | Class _ | Continue | Define _ | Delete _ | Expression _ | Import _ | Nonlocal _ | Pass | Raise _ | Return _ -> sofar and globals_of_statements sofar statements = List.fold statements ~init:sofar ~f:globals_of_statement let rec nonlocals_of_statement sofar { Node.value; _ } = let open Statement in match value with | Statement.Nonlocal nonlocals -> List.rev_append nonlocals sofar | For { For.body; orelse; _ } | If { If.body; orelse; _ } | While { While.body; orelse; _ } -> let sofar = nonlocals_of_statements sofar body in nonlocals_of_statements sofar orelse | Match { Match.cases; _ } -> List.fold cases ~init:sofar ~f:(fun sofar { Match.Case.body; _ } -> nonlocals_of_statements sofar body) | Try { Try.body; handlers; orelse; finally } -> let sofar = nonlocals_of_statements sofar body in let sofar = List.fold handlers ~init:sofar ~f:(fun sofar { Try.Handler.body; _ } -> nonlocals_of_statements sofar body) in let sofar = nonlocals_of_statements sofar orelse in nonlocals_of_statements sofar finally | With { With.body; _ } -> nonlocals_of_statements sofar body | Assign _ | Assert _ | Break | Class _ | Continue | Define _ | Delete _ | Expression _ | Global _ | Import _ | Pass | Raise _ | Return _ -> sofar and nonlocals_of_statements sofar statements = List.fold statements ~init:sofar ~f:nonlocals_of_statement module Scope = struct module Kind = struct type t = | Module | Define of Statement.Define.Signature.t | Lambda | Comprehension [@@deriving sexp, compare, hash] end type t = { kind: Kind.t; globals: Identifier.Set.t; nonlocals: Identifier.Set.t; bindings: Binding.t Identifier.Map.t; } [@@deriving sexp, compare] let create_map ~globals ~nonlocals = List.fold ~init:Identifier.Map.empty ~f:(fun sofar ({ Binding.name; _ } as binding) -> match Identifier.Set.mem globals name || Identifier.Set.mem nonlocals name with | true -> (* Global and nonlocal declarations take priority. *) sofar | false -> (* First binding (i.e. last item in the list) wins. *) Map.set sofar ~key:name ~data:binding) let of_define { Statement.Define.signature; body; _ } = let open Statement in if Define.Signature.is_toplevel signature then None else let globals = globals_of_statements [] body |> Identifier.Set.of_list in let nonlocals = nonlocals_of_statements [] body |> Identifier.Set.of_list in let bindings = let parameter_bindings = let { Define.Signature.parameters; _ } = signature in Binding.of_parameters [] parameters in Binding.of_statements parameter_bindings body in Some { kind = Kind.Define signature; globals; nonlocals; bindings = create_map ~globals ~nonlocals bindings; } let of_define_exn define = match of_define define with | None -> let message = Format.asprintf "Cannot create a scope for define %a" Reference.pp (Statement.Define.name define) in failwith message | Some result -> result let of_expression { Node.value; _ } = let open Expression in (* There's no way to declare globals or nonlocals in expression scope. *) let globals = Identifier.Set.empty in let nonlocals = Identifier.Set.empty in match value with | Expression.Lambda { Lambda.parameters; body } -> let bindings = let parameter_bindings = Binding.of_parameters [] parameters in Binding.of_expression parameter_bindings body in Some { kind = Kind.Lambda; globals; nonlocals; bindings = create_map ~globals ~nonlocals bindings; } | DictionaryComprehension { Comprehension.generators; _ } | Generator { Comprehension.generators; _ } | ListComprehension { Comprehension.generators; _ } | SetComprehension { Comprehension.generators; _ } -> (* PEP-572 counts these bindings (along with bindings in `element`) towards the containing scope *) let is_not_walrus_binding { Binding.kind; _ } = match kind with | Binding.Kind.WalrusTarget -> false | _ -> true in Some { kind = Kind.Comprehension; globals; nonlocals; bindings = Binding.of_generators [] generators |> List.filter ~f:is_not_walrus_binding |> create_map ~globals ~nonlocals; } | _ -> None let of_expression_exn expression = match of_expression expression with | None -> let message = Format.asprintf "Cannot create a scope for expression %a" Expression.pp expression in failwith message | Some result -> result let of_source { Source.statements; _ } = let globals = (* Global statement has no effect at the module level. *) Identifier.Set.empty in let nonlocals = (* Nonlocal statement is illegal at the module level. *) Identifier.Set.empty in { kind = Kind.Module; globals; nonlocals; bindings = Binding.of_statements [] statements |> create_map ~globals ~nonlocals; } let lookup_bindings { bindings; _ } = Map.find bindings module Lookup = struct type t = | Binding of Binding.t | Global | Nonlocal [@@deriving sexp, compare, hash] end let lookup { globals; nonlocals; bindings; _ } name = match Set.mem globals name with | true -> Some Lookup.Global | false -> ( match Set.mem nonlocals name with | true -> Some Lookup.Nonlocal | false -> Map.find bindings name >>| fun binding -> Lookup.Binding binding) end module Access = struct module Locality = struct type t = | Local | Nonlocal | Global [@@deriving sexp, compare, hash] end module Kind = struct type t = | CurrentScope | OuterScope of Locality.t [@@deriving sexp, compare, hash] end type t = { kind: Kind.t; binding: Binding.t; scope: Scope.t; } [@@deriving sexp, compare] end module ScopeStack = struct type t = { global: Scope.t; locals: Scope.t list; } let create source = { global = Scope.of_source source; locals = [] } let global_scope { global; _ } = global let current_scope { locals; global } = match locals with | [] -> global | scope :: _ -> scope let lookup { locals; global } name = let rec lookup_outer_scopes ~exclude_global = function | [] -> if exclude_global then None else Scope.lookup_bindings global name >>| fun binding -> global, binding | scope :: rest -> ( match Scope.lookup scope name with | Some (Scope.Lookup.Binding binding) -> Some (scope, binding) | Some Scope.Lookup.Global -> lookup_outer_scopes ~exclude_global [] | _ -> lookup_outer_scopes ~exclude_global rest) in match locals with | [] -> (* No need to deal with nested scopes if there's none *) Scope.lookup_bindings global name >>| fun binding -> { Access.binding; scope = global; kind = Access.Kind.CurrentScope } | current_scope :: rest -> ( match Scope.lookup current_scope name with | Some (Scope.Lookup.Binding binding) -> (* Binding found in the current scope *) Some { Access.binding; scope = current_scope; kind = Access.Kind.CurrentScope } | Some Scope.Lookup.Global -> Scope.lookup_bindings global name >>| fun binding -> { Access.binding; scope = global; kind = Access.(Kind.OuterScope Locality.Global) } | Some Scope.Lookup.Nonlocal -> lookup_outer_scopes ~exclude_global:true rest >>| fun (scope, binding) -> { Access.binding; scope; kind = Access.(Kind.OuterScope Locality.Nonlocal) } | None -> lookup_outer_scopes ~exclude_global:false rest >>| fun (scope, binding) -> { Access.binding; scope; kind = Access.(Kind.OuterScope Locality.Local) }) let extend ~with_ { global; locals } = { global; locals = with_ :: locals } end module Builtins = struct let mem = function (* Builtins recognized by Pyre itself *) | "BoundMethod" | "pyre_dump" | "reveal_type" (* Builtins recognized by Python *) | "__build_class__" | "__builtins__" | "__debug__" | "__dict__" | "__doc__" | "__file__" | "__import__" | "__loader__" | "__name__" | "__package__" | "__path__" | "__spec__" | "ArithmeticError" | "AssertionError" | "AttributeError" | "BaseException" | "BlockingIOError" | "BrokenPipeError" | "BufferError" | "BytesWarning" | "ChildProcessError" | "ConnectionAbortedError" | "ConnectionError" | "ConnectionRefusedError" | "ConnectionResetError" | "DeprecationWarning" | "EOFError" | "Ellipsis" | "EnvironmentError" | "Exception" | "False" | "FileExistsError" | "FileNotFoundError" | "FloatingPointError" | "FutureWarning" | "GeneratorExit" | "IOError" | "ImportError" | "ImportWarning" | "IndentationError" | "IndexError" | "InterruptedError" | "IsADirectoryError" | "KeyError" | "KeyboardInterrupt" | "LookupError" | "MemoryError" | "ModuleNotFoundError" | "NameError" | "None" | "NotADirectoryError" | "NotImplemented" | "NotImplementedError" | "OSError" | "OverflowError" | "PendingDeprecationWarning" | "PermissionError" | "ProcessLookupError" | "RecursionError" | "ReferenceError" | "ResourceWarning" | "RuntimeError" | "RuntimeWarning" | "StopAsyncIteration" | "StopIteration" | "SyntaxError" | "SyntaxWarning" | "SystemError" | "SystemExit" | "TabError" | "TimeoutError" | "True" | "TypeError" | "UnboundLocalError" | "UnicodeDecodeError" | "UnicodeEncodeError" | "UnicodeError" | "UnicodeTranslateError" | "UnicodeWarning" | "UserWarning" | "ValueError" | "Warning" | "ZeroDivisionError" | "abs" | "all" | "any" | "ascii" | "bin" | "bool" | "breakpoint" | "bytearray" | "bytes" | "callable" | "chr" | "classmethod" | "compile" | "complex" | "copyright" | "credits" | "delattr" | "dict" | "dir" | "divmod" | "enumerate" | "eval" | "exec" | "exit" | "filter" | "float" | "format" | "frozenset" | "getattr" | "globals" | "hasattr" | "hash" | "help" | "hex" | "id" | "input" | "int" | "isinstance" | "issubclass" | "iter" | "len" | "license" | "list" | "locals" | "map" | "max" | "memoryview" | "min" | "next" | "object" | "oct" | "open" | "ord" | "pow" | "print" | "property" | "quit" | "range" | "repr" | "reversed" | "round" | "set" | "setattr" | "slice" | "sorted" | "staticmethod" | "str" | "sum" | "super" | "tuple" | "type" | "vars" | "zip" -> true | _ -> false end