Summary: This is a helper module for reading info from a 3rd party nullability repository. Next diffs are going to use it for reading nullability repository from disk. Reviewed By: artempyanykh Differential Revision: D18225473 fbshipit-source-id: 06a2dc97emaster
parent
7b4f8a4bbc
commit
688deb0936
@ -0,0 +1,134 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
open! IStd
|
||||
module F = Format
|
||||
open Result.Monad_infix
|
||||
|
||||
type fully_qualified_type = string [@@deriving sexp]
|
||||
|
||||
type unique_repr =
|
||||
{ class_name: fully_qualified_type
|
||||
; method_name: method_name
|
||||
; param_types: fully_qualified_type list }
|
||||
[@@deriving sexp]
|
||||
|
||||
and method_name = Constructor | Method of string
|
||||
|
||||
type type_nullability = Nullable | Nonnull [@@deriving sexp]
|
||||
|
||||
type nullability = {ret_nullability: type_nullability; param_nullability: type_nullability list}
|
||||
[@@deriving sexp]
|
||||
|
||||
type parsing_error = BadStructure | MalformedNullability | LackingParam | MalformedParam
|
||||
|
||||
let string_of_parsing_error = function
|
||||
| BadStructure ->
|
||||
"BadStructure"
|
||||
| MalformedNullability ->
|
||||
"MalformedNullability"
|
||||
| LackingParam ->
|
||||
"LackingParam"
|
||||
| MalformedParam ->
|
||||
"MalformedParam"
|
||||
|
||||
|
||||
let pp_unique_repr fmt signature = Sexp.pp fmt (sexp_of_unique_repr signature)
|
||||
|
||||
let pp_nullability fmt nullability = Sexp.pp fmt (sexp_of_nullability nullability)
|
||||
|
||||
let parse_nullability str =
|
||||
match String.strip str with
|
||||
| "@Nullable" ->
|
||||
Ok Nullable
|
||||
| "" ->
|
||||
Ok Nonnull
|
||||
| _ ->
|
||||
Error MalformedNullability
|
||||
|
||||
|
||||
let whitespace_no_line_break = lazy (Str.regexp "[ \t]+")
|
||||
|
||||
let parse_param str =
|
||||
let trimmed_param = String.strip str in
|
||||
match Str.split (Lazy.force whitespace_no_line_break) trimmed_param with
|
||||
| [nullability_str; typ] ->
|
||||
parse_nullability nullability_str >>= fun nullability -> Ok (typ, nullability)
|
||||
| [typ] ->
|
||||
Ok (typ, Nonnull)
|
||||
| [] ->
|
||||
Error LackingParam
|
||||
| _ ->
|
||||
Error MalformedParam
|
||||
|
||||
|
||||
(* Given a list of results and a binding function, returns Ok (results) if all results
|
||||
are Ok or the first error if any of results has Error *)
|
||||
let bind_list list_of_results ~f =
|
||||
List.fold list_of_results ~init:(Ok []) ~f:(fun acc element ->
|
||||
acc
|
||||
>>= fun accumulated_success_results ->
|
||||
f element >>= fun success_result -> Ok (accumulated_success_results @ [success_result]) )
|
||||
|
||||
|
||||
let split_params str =
|
||||
let stripped = String.strip str in
|
||||
(* Empty case is the special one: lack of params mean an empty list,
|
||||
not a list of a single empty string *)
|
||||
if String.is_empty stripped then [] else String.split stripped ~on:','
|
||||
|
||||
|
||||
let parse_params str = split_params str |> bind_list ~f:parse_param
|
||||
|
||||
let parse_method_name str =
|
||||
match String.strip str with "<init>" -> Constructor | _ as method_name -> Method method_name
|
||||
|
||||
|
||||
let match_after_close_brace (split_result : Str.split_result list) =
|
||||
(* After close brace there can be either nothing or return type nullability information *)
|
||||
match split_result with
|
||||
| [] ->
|
||||
parse_nullability ""
|
||||
| [Text nullability] ->
|
||||
parse_nullability nullability
|
||||
| _ ->
|
||||
Error BadStructure
|
||||
|
||||
|
||||
let match_after_open_brace (split_result : Str.split_result list) =
|
||||
(* We expect to see <params>)<nullability>, so let's look for `)` *)
|
||||
( match split_result with
|
||||
| Text params :: Delim ")" :: rest ->
|
||||
Ok (params, rest)
|
||||
| Delim ")" :: rest ->
|
||||
Ok ("", rest)
|
||||
| _ ->
|
||||
Error BadStructure )
|
||||
>>= fun (params, rest) ->
|
||||
parse_params params
|
||||
>>= fun parsed_params ->
|
||||
match_after_close_brace rest >>= fun ret_nullability -> Ok (parsed_params, ret_nullability)
|
||||
|
||||
|
||||
let hashsign_and_parentheses = lazy (Str.regexp "[#()]")
|
||||
|
||||
let parse str =
|
||||
(* Expected string is <Class>#<method>(<params>)<ret_nullability>,
|
||||
let's look what is between #, (, and ) *)
|
||||
match Str.full_split (Lazy.force hashsign_and_parentheses) str with
|
||||
| Text class_name_str :: Delim "#" :: Text method_name_str :: Delim "(" :: rest ->
|
||||
let method_name = parse_method_name method_name_str in
|
||||
let class_name = String.strip class_name_str in
|
||||
match_after_open_brace rest
|
||||
>>= fun (parsed_params, ret_nullability) ->
|
||||
let param_types, param_nullability = List.unzip parsed_params in
|
||||
Ok ({class_name; method_name; param_types}, {ret_nullability; param_nullability})
|
||||
| _ ->
|
||||
Error BadStructure
|
||||
|
||||
|
||||
let pp_parse_result fmt (unique_repr, nullability) =
|
||||
F.fprintf fmt "(%a; %a)" pp_unique_repr unique_repr pp_nullability nullability
|
@ -0,0 +1,45 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
(** A helper module responsible for representing nullability information for a single 3rd party method,
|
||||
as well with functionality to read this information from the 3rd party nullability repository.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
(** E.g. "full.package.name.TypeName$NestedTypeName1$NestedTypeName2"
|
||||
*)
|
||||
type fully_qualified_type = string
|
||||
|
||||
(** The minimum information that is needed to _uniquely_ identify the method.
|
||||
That why we don't include e.g. return type, access quilifiers, or whether the method is static
|
||||
(because Java overload resolution rules ignore these things).
|
||||
In contrast, parameter types are essential, because Java allows several methods with different types.
|
||||
*)
|
||||
type unique_repr =
|
||||
{ class_name: fully_qualified_type
|
||||
; method_name: method_name
|
||||
; param_types: fully_qualified_type list }
|
||||
|
||||
and method_name = Constructor | Method of string
|
||||
|
||||
type nullability = {ret_nullability: type_nullability; param_nullability: type_nullability list}
|
||||
|
||||
and type_nullability = Nullable | Nonnull
|
||||
|
||||
type parsing_error
|
||||
|
||||
val string_of_parsing_error : parsing_error -> string
|
||||
|
||||
val parse : string -> (unique_repr * nullability, parsing_error) result
|
||||
(** Given a string representing nullability information for a given third-party method,
|
||||
return the method signature and nullability of its params and return values.
|
||||
The string should come from a repository storing 3rd party annotations.
|
||||
E.g.
|
||||
"package.name.Class$NestedClass#foo(package.name.SomeClass, @Nullable package.name.OtherClass) @Nullable" *)
|
||||
|
||||
val pp_parse_result : Format.formatter -> unique_repr * nullability -> unit
|
@ -0,0 +1,10 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
let tests = [ThirdPartyMethodTests.test]
|
@ -0,0 +1,10 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
|
||||
val tests : OUnit2.test list
|
@ -0,0 +1,113 @@
|
||||
(*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
open! IStd
|
||||
module F = Format
|
||||
open OUnit2
|
||||
open ThirdPartyMethod
|
||||
|
||||
let assert_parse_ok input expected_output =
|
||||
let result = ThirdPartyMethod.parse input in
|
||||
match result with
|
||||
| Ok output ->
|
||||
assert_equal output expected_output ~printer:(fun parse_result ->
|
||||
Pp.string_of_pp pp_parse_result parse_result )
|
||||
| Error error ->
|
||||
assert_failure
|
||||
(F.asprintf "Expected '%s' to be parsed as %a, but got error %s instead" input
|
||||
ThirdPartyMethod.pp_parse_result expected_output (string_of_parsing_error error))
|
||||
|
||||
|
||||
let assert_parse_bad input =
|
||||
let result = ThirdPartyMethod.parse input in
|
||||
match result with
|
||||
| Ok output ->
|
||||
assert_failure
|
||||
(F.asprintf "Expected '%s' to be NOT parsed, but was parsed as %a instead" input
|
||||
ThirdPartyMethod.pp_parse_result output)
|
||||
| Error _ ->
|
||||
()
|
||||
|
||||
|
||||
let success_cases =
|
||||
"success_cases"
|
||||
>:: fun _ ->
|
||||
(* No params *)
|
||||
assert_parse_ok "a.b.C#foo()"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= []}
|
||||
, {ret_nullability= Nonnull; param_nullability= []} ) ;
|
||||
assert_parse_ok " a.b.C # foo ( ) "
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= []}
|
||||
, {ret_nullability= Nonnull; param_nullability= []} ) ;
|
||||
assert_parse_ok "a.b.C#foo() @Nullable"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= []}
|
||||
, {ret_nullability= Nullable; param_nullability= []} ) ;
|
||||
assert_parse_ok "a.b.C # foo( ) @Nullable "
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= []}
|
||||
, {ret_nullability= Nullable; param_nullability= []} ) ;
|
||||
(* One param *)
|
||||
assert_parse_ok "a.b.C#foo(c.d.E)"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"]}
|
||||
, {ret_nullability= Nonnull; param_nullability= [Nonnull]} ) ;
|
||||
assert_parse_ok "a.b.C#foo(@Nullable c.d.E)"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"]}
|
||||
, {ret_nullability= Nonnull; param_nullability= [Nullable]} ) ;
|
||||
assert_parse_ok "a.b.C#foo(c.d.E) @Nullable"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"]}
|
||||
, {ret_nullability= Nullable; param_nullability= [Nonnull]} ) ;
|
||||
assert_parse_ok "a.b.C#foo(@Nullable c.d.E) @Nullable"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"]}
|
||||
, {ret_nullability= Nullable; param_nullability= [Nullable]} ) ;
|
||||
assert_parse_ok " a.b.C # foo ( @Nullable c.d.E ) @Nullable "
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"]}
|
||||
, {ret_nullability= Nullable; param_nullability= [Nullable]} ) ;
|
||||
(* Many params *)
|
||||
assert_parse_ok "a.b.C#foo(c.d.E,a.b.C,x.y.Z)"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"; "a.b.C"; "x.y.Z"]}
|
||||
, {ret_nullability= Nonnull; param_nullability= [Nonnull; Nonnull; Nonnull]} ) ;
|
||||
assert_parse_ok "a.b.C#foo(c.d.E, @Nullable a.b.C, x.y.Z)"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"; "a.b.C"; "x.y.Z"]}
|
||||
, {ret_nullability= Nonnull; param_nullability= [Nonnull; Nullable; Nonnull]} ) ;
|
||||
assert_parse_ok "a.b.C#foo(@Nullable c.d.E, a.b.C, @Nullable x.y.Z) @Nullable"
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"; "a.b.C"; "x.y.Z"]}
|
||||
, {ret_nullability= Nullable; param_nullability= [Nullable; Nonnull; Nullable]} ) ;
|
||||
assert_parse_ok
|
||||
"a.b.C # foo ( @Nullable c.d.E , a.b.C , @Nullable x.y.Z ) @Nullable "
|
||||
( {class_name= "a.b.C"; method_name= Method "foo"; param_types= ["c.d.E"; "a.b.C"; "x.y.Z"]}
|
||||
, {ret_nullability= Nullable; param_nullability= [Nullable; Nonnull; Nullable]} ) ;
|
||||
(* Constructor *)
|
||||
assert_parse_ok "a.b.C#<init>(@Nullable c.d.E, a.b.C, x.y.Z) @Nullable"
|
||||
( {class_name= "a.b.C"; method_name= Constructor; param_types= ["c.d.E"; "a.b.C"; "x.y.Z"]}
|
||||
, {ret_nullability= Nullable; param_nullability= [Nullable; Nonnull; Nonnull]} )
|
||||
|
||||
|
||||
(* We intentionally don't test all bad cases.
|
||||
It is generally OK for nullsafe to allow something that is not really valid:
|
||||
We let other tools to make thorough linting.
|
||||
Also we don't test exact error type, because this is an implementation detail
|
||||
needed merely to simplify diagnostics
|
||||
*)
|
||||
let bad_cases =
|
||||
"bad_cases"
|
||||
>:: fun _ ->
|
||||
assert_parse_bad "" ;
|
||||
assert_parse_bad " " ;
|
||||
assert_parse_bad "blablabla" ;
|
||||
assert_parse_bad "a.b.C.f()" ;
|
||||
(* no # delimiter *)
|
||||
assert_parse_bad "a.b.C#f(())" ;
|
||||
(* nested parenthesis *)
|
||||
assert_parse_bad "a.b.C#f(int param)" ;
|
||||
(* param names are not accepted *)
|
||||
assert_parse_bad "a.b.C#f(Nullable param)" ;
|
||||
(* Missed @ in annotation*)
|
||||
assert_parse_bad "a.b.C#f(@Nullable int param)"
|
||||
|
||||
|
||||
(* param names are not accepted *)
|
||||
|
||||
let test = "ThirdPartyMethodTests" >::: [success_cases; bad_cases]
|
Loading…
Reference in new issue