Summary: In next diffs, we will: 1/ teach nullsafe to read nullability information from the 3rd party annotation folder 2/ use this storage in addition to our hard-coded models to respect nullability during type-checking Reviewed By: artempyanykh Differential Revision: D18247538 fbshipit-source-id: ee45bc80emaster
parent
d50091bb17
commit
7ea42938fe
@ -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.
|
||||
*)
|
||||
open! IStd
|
||||
module Hashtbl = Caml.Hashtbl
|
||||
|
||||
type storage = (ThirdPartyMethod.unique_repr, ThirdPartyMethod.nullability) Hashtbl.t
|
||||
|
||||
let create_storage () = Hashtbl.create 1
|
||||
|
||||
type file_parsing_error =
|
||||
{line_number: int; unparsable_method: string; parsing_error: ThirdPartyMethod.parsing_error}
|
||||
|
||||
let pp_parsing_error fmt {line_number; unparsable_method; parsing_error} =
|
||||
Format.fprintf fmt "Line %d: Could not parse method '%s': %s" line_number unparsable_method
|
||||
(ThirdPartyMethod.string_of_parsing_error parsing_error)
|
||||
|
||||
|
||||
(* Consequtively evaluates results for all elements in a list,
|
||||
returns Ok () if all succeeded or the first error.
|
||||
The evaluator function [f] has access to element's index.
|
||||
*)
|
||||
let bind_list_with_index list ~f =
|
||||
List.foldi list ~init:(Ok ()) ~f:(fun index acc elem ->
|
||||
Result.bind acc ~f:(fun _ -> f index elem) )
|
||||
|
||||
|
||||
let parse_line_and_add_to_storage storage line =
|
||||
let open Result in
|
||||
ThirdPartyMethod.parse line
|
||||
>>= fun (signature, nullability) -> Ok (Hashtbl.add storage signature nullability)
|
||||
|
||||
|
||||
let add_from_signature_file storage ~lines =
|
||||
(* each line in a file should represent a method signature *)
|
||||
bind_list_with_index lines ~f:(fun index method_as_str ->
|
||||
parse_line_and_add_to_storage storage method_as_str
|
||||
|> Result.map_error ~f:(fun parsing_error ->
|
||||
{line_number= index + 1; unparsable_method= method_as_str; parsing_error} ) )
|
||||
|
||||
|
||||
let find_nullability_info storage unique_repr = Hashtbl.find_opt storage unique_repr
|
@ -0,0 +1,28 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(** In-memory storage the information about nullability annotation of third-party methods. *)
|
||||
|
||||
type storage
|
||||
|
||||
val create_storage : unit -> storage
|
||||
|
||||
type file_parsing_error =
|
||||
{line_number: int; unparsable_method: string; parsing_error: ThirdPartyMethod.parsing_error}
|
||||
|
||||
val pp_parsing_error : Format.formatter -> file_parsing_error -> unit
|
||||
|
||||
val add_from_signature_file : storage -> lines:string list -> (unit, file_parsing_error) result
|
||||
(** Parse the information from the signature file, and add it to the storage *)
|
||||
|
||||
val find_nullability_info :
|
||||
storage -> ThirdPartyMethod.unique_repr -> ThirdPartyMethod.nullability option
|
||||
(** The main method. Do we have an information about the third-party method?
|
||||
If we do not, or it is not a third-party method, returns None.
|
||||
Otherwise returns the nullability information.
|
||||
*)
|
@ -0,0 +1,190 @@
|
||||
(*
|
||||
* 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
|
||||
open OUnit2
|
||||
module F = Format
|
||||
|
||||
let assert_has_nullability_info storage unique_repr ~expected_nullability =
|
||||
match ThirdPartyAnnotationInfo.find_nullability_info storage unique_repr with
|
||||
| None ->
|
||||
assert_failure
|
||||
(F.asprintf "Expected to find info for %a, but it was not found"
|
||||
ThirdPartyMethod.pp_unique_repr unique_repr)
|
||||
| Some nullability ->
|
||||
assert_equal expected_nullability nullability
|
||||
~msg:
|
||||
(F.asprintf "Nullability info for %a does not match" ThirdPartyMethod.pp_unique_repr
|
||||
unique_repr)
|
||||
~printer:(Pp.string_of_pp ThirdPartyMethod.pp_nullability)
|
||||
|
||||
|
||||
let assert_no_info storage unique_repr =
|
||||
match ThirdPartyAnnotationInfo.find_nullability_info storage unique_repr with
|
||||
| None ->
|
||||
()
|
||||
| Some nullability ->
|
||||
assert_failure
|
||||
(F.asprintf "Did not expect to find nullability info for method %a, but found %a"
|
||||
ThirdPartyMethod.pp_unique_repr unique_repr ThirdPartyMethod.pp_nullability nullability)
|
||||
|
||||
|
||||
let add_from_annot_file_and_check_success storage ~lines =
|
||||
ThirdPartyAnnotationInfo.add_from_signature_file storage ~lines
|
||||
|> Result.iter_error ~f:(fun parsing_error ->
|
||||
assert_failure
|
||||
(F.asprintf "Expected to parse the file, but it was unparsable: %a"
|
||||
ThirdPartyAnnotationInfo.pp_parsing_error parsing_error) )
|
||||
|
||||
|
||||
let add_from_annot_file_and_check_failure storage ~lines ~expected_error_line_number =
|
||||
match ThirdPartyAnnotationInfo.add_from_signature_file storage ~lines with
|
||||
| Ok () ->
|
||||
assert_failure
|
||||
"Expected to not be able to parse the file, but it was successfully parsed instead"
|
||||
| Error {line_number} ->
|
||||
assert_equal expected_error_line_number line_number ~msg:"Error line number does not match"
|
||||
~printer:string_of_int
|
||||
|
||||
|
||||
let basic_find =
|
||||
let open ThirdPartyMethod in
|
||||
"basic_find"
|
||||
>:: fun _ ->
|
||||
let storage = ThirdPartyAnnotationInfo.create_storage () in
|
||||
let lines = ["a.A#foo(b.B)"; "b.B#bar(c.C, @Nullable d.D) @Nullable"] in
|
||||
(* Load some functions from the file *)
|
||||
add_from_annot_file_and_check_success storage ~lines ;
|
||||
(* Make sure we can find what we just stored *)
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ;
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "b.B"; method_name= Method "bar"; param_types= ["c.C"; "d.D"]}
|
||||
~expected_nullability:{ret_nullability= Nullable; param_nullability= [Nonnull; Nullable]} ;
|
||||
(* Make sure we can not find stuff we did not store *)
|
||||
(* Wrong class name *)
|
||||
assert_no_info storage {class_name= "a.AB"; method_name= Method "foo"; param_types= ["b.B"]} ;
|
||||
(* Wrong method name *)
|
||||
assert_no_info storage {class_name= "a.A"; method_name= Method "foo1"; param_types= ["b.B"]} ;
|
||||
(* Wrong param type *)
|
||||
assert_no_info storage {class_name= "a.A"; method_name= Method "foo"; param_types= ["c.C"]} ;
|
||||
(* Not enough params *)
|
||||
assert_no_info storage {class_name= "a.A"; method_name= Method "foo"; param_types= []} ;
|
||||
(* Too many params *)
|
||||
assert_no_info storage {class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"; "c.C"]}
|
||||
|
||||
|
||||
let overload_resolution =
|
||||
let open ThirdPartyMethod in
|
||||
"overload_resolution"
|
||||
>:: fun _ ->
|
||||
let storage = ThirdPartyAnnotationInfo.create_storage () in
|
||||
let lines =
|
||||
[ "a.b.SomeClass#foo(@Nullable a.b.C1) @Nullable"
|
||||
; "a.b.SomeClass#<init>(a.b.C1)"
|
||||
; "a.b.SomeClass#foo(@Nullable a.b.C1, @Nullable a.b.C3, c.d.C4) @Nullable"
|
||||
; "c.d.OtherClass#foo(@Nullable a.b.C2)"
|
||||
; "a.b.SomeClass#<init>()"
|
||||
; "a.b.SomeClass#<init>(@Nullable a.b.C2)"
|
||||
; "a.b.SomeClass#foo(@Nullable a.b.C2)" ]
|
||||
in
|
||||
(* Load some functions from the file *)
|
||||
add_from_annot_file_and_check_success storage ~lines ;
|
||||
(* Make sure we can find what we just stored *)
|
||||
(* a.b.SomeClass.foo with 1 param *)
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Method "foo"; param_types= ["a.b.C1"]}
|
||||
~expected_nullability:{ret_nullability= Nullable; param_nullability= [Nullable]} ;
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Method "foo"; param_types= ["a.b.C2"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nullable]} ;
|
||||
(* wrong type *)
|
||||
assert_no_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Method "foo"; param_types= ["a.b.C3"]} ;
|
||||
(* wrong class *)
|
||||
assert_no_info storage
|
||||
{class_name= "a.b.c.SomeClass"; method_name= Method "foo"; param_types= ["a.b.C1"]} ;
|
||||
(* wrong method name *)
|
||||
assert_no_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Method "bar"; param_types= ["a.b.C1"]} ;
|
||||
(* a.b.SomeClass.foo with many params *)
|
||||
assert_has_nullability_info storage
|
||||
{ class_name= "a.b.SomeClass"
|
||||
; method_name= Method "foo"
|
||||
; param_types= ["a.b.C1"; "a.b.C3"; "c.d.C4"] }
|
||||
~expected_nullability:
|
||||
{ret_nullability= Nullable; param_nullability= [Nullable; Nullable; Nonnull]} ;
|
||||
(* wrong param order *)
|
||||
assert_no_info storage
|
||||
{ class_name= "a.b.SomeClass"
|
||||
; method_name= Method "foo"
|
||||
; param_types= ["a.b.C3"; "a.b.C1"; "c.d.C4"] } ;
|
||||
(* third param is missing *)
|
||||
assert_no_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Method "foo"; param_types= ["a.b.C1"; "a.b.C3"]} ;
|
||||
(* possibility of constructor overload should be respected *)
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Constructor; param_types= []}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= []} ;
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Constructor; param_types= ["a.b.C1"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ;
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Constructor; param_types= ["a.b.C2"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nullable]} ;
|
||||
(* wrong param type *)
|
||||
assert_no_info storage
|
||||
{class_name= "a.b.SomeClass"; method_name= Constructor; param_types= ["a.b.C3"]}
|
||||
|
||||
|
||||
let can_add_several_files =
|
||||
"can_add_several_files"
|
||||
>:: fun _ ->
|
||||
let open ThirdPartyMethod in
|
||||
let storage = ThirdPartyAnnotationInfo.create_storage () in
|
||||
(* 1. Add file and check if we added info *)
|
||||
let file1 = ["a.A#foo(b.B)"; "b.B#bar(c.C, @Nullable d.D) @Nullable"] in
|
||||
add_from_annot_file_and_check_success storage ~lines:file1 ;
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ;
|
||||
(* 2. Add another file and check if we added info *)
|
||||
let file2 = ["e.E#baz(f.F)"; "g.G#<init>(h.H, @Nullable i.I) @Nullable"] in
|
||||
add_from_annot_file_and_check_success storage ~lines:file2 ;
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "e.E"; method_name= Method "baz"; param_types= ["f.F"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]} ;
|
||||
(* 3. Ensure we did not forget the content from the first file *)
|
||||
assert_has_nullability_info storage
|
||||
{class_name= "a.A"; method_name= Method "foo"; param_types= ["b.B"]}
|
||||
~expected_nullability:{ret_nullability= Nonnull; param_nullability= [Nonnull]}
|
||||
|
||||
|
||||
let should_not_forgive_unparsable_strings =
|
||||
"should_not_forgive_unparsable_strings"
|
||||
>:: fun _ ->
|
||||
let line1 = "a.b.SomeClass#foo(@Nullable a.b.C1) @Nullable" in
|
||||
let line2_ok = "a.b.SomeClass#<init>(a.b.C1)" in
|
||||
(* like line2_ok, but one extra open parenthesis *)
|
||||
let line2_bad = "a.b.SomeClass#<init>((a.b.C1)" in
|
||||
let line3 = "a.b.SomeClass#foo(@Nullable a.b.C1, @Nullable a.b.C3, c.d.C4) @Nullable" in
|
||||
let file_ok = [line1; line2_ok; line3] in
|
||||
let file_bad = [line1; line2_bad; line3] in
|
||||
(* Ensure we can add the good file, but can not add the bad one *)
|
||||
add_from_annot_file_and_check_success (ThirdPartyAnnotationInfo.create_storage ()) ~lines:file_ok ;
|
||||
add_from_annot_file_and_check_failure
|
||||
(ThirdPartyAnnotationInfo.create_storage ())
|
||||
~lines:file_bad ~expected_error_line_number:2
|
||||
|
||||
|
||||
let test =
|
||||
"ThirdPartyAnnotationInfoTests"
|
||||
>::: [ basic_find
|
||||
; overload_resolution
|
||||
; can_add_several_files
|
||||
; should_not_forgive_unparsable_strings ]
|
Loading…
Reference in new issue