├── .codecov.yml ├── .editorconfig ├── .github └── workflows │ ├── build-and-test.yml │ ├── proptests.yml │ └── self-check.yml ├── .gitignore ├── .travis.yml ├── .travis ├── after_success └── travis_has_latest_otp_version ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── Notes.md ├── README.md ├── doc ├── GTLC.hs ├── examples.md ├── screenshots │ ├── 134913123-4700b299-2714-4227-b7e2-f8c816b138a5.gif │ └── exhaustive_type.png └── typesystem.md ├── erlang_ls.config ├── examples └── rebar3 │ ├── README.md │ ├── rebar.config │ └── src │ ├── not_included.erl │ ├── rebar3_example.app.src │ └── rebar3_example.erl ├── gradualize-ignore.lst ├── include └── gradualizer.hrl ├── priv ├── extra_specs │ └── epp.specs.erl ├── prelude │ ├── README │ ├── erlang.specs.erl │ ├── filename.specs.erl │ └── lists.specs.erl └── test │ └── undefined_errors_helper.erl ├── rebar.config ├── src ├── absform.erl ├── constraints.erl ├── constraints.hrl ├── gradualizer.app.src ├── gradualizer.erl ├── gradualizer_app.erl ├── gradualizer_bin.erl ├── gradualizer_cache.erl ├── gradualizer_cli.erl ├── gradualizer_db.erl ├── gradualizer_file_utils.erl ├── gradualizer_fmt.erl ├── gradualizer_highlight.erl ├── gradualizer_int.erl ├── gradualizer_lib.erl ├── gradualizer_prelude.erl ├── gradualizer_prelude_parse_trans.erl ├── gradualizer_sup.erl ├── gradualizer_tracer.erl ├── gradualizer_type.erl ├── rebar_prv_gradualizer.erl ├── typechecker.erl ├── typechecker.hrl ├── typelib.erl ├── typelib.hrl └── user_default.erl ├── talks ├── Makefile ├── abstract.txt └── talk.md └── test ├── absform_tests.erl ├── check_name_clashes.sh ├── constraints_tests.erl ├── dir └── test_in_dir.erl ├── gradualizer_bin_tests.erl ├── gradualizer_cli_tests.erl ├── gradualizer_db_tests.erl ├── gradualizer_highlight_tests.erl ├── gradualizer_int_tests.erl ├── gradualizer_prop_SUITE.erl ├── gradualizer_tests.erl ├── known_problems ├── README.md ├── should_fail │ ├── README.md │ ├── arith_op.erl │ ├── binary_comprehension.erl │ ├── case_pattern_should_fail.erl │ ├── exhaustive_argumentwise.erl │ ├── exhaustive_expr.erl │ ├── exhaustive_map_variants.erl │ ├── exhaustive_remote_map_variants.erl │ ├── guard_should_fail.erl │ ├── infer_any_pattern.erl │ ├── intersection_with_any_should_fail.erl │ ├── intersection_with_unreachable.erl │ ├── lambda_wrong_args.erl │ ├── map_refinement_fancy.erl │ ├── maybe_expr_should_fail.erl │ ├── poly_lists_map_should_fail.erl │ ├── poly_should_fail.erl │ ├── recursive_types_should_fail.erl │ ├── refine_ty_vars.erl │ ├── sample.erl │ └── type_refinement_should_fail.erl └── should_pass │ ├── README.md │ ├── arith_op_arg_types.erl │ ├── binary_exhaustiveness_checking_should_pass.erl │ ├── call_intersection_function_with_union_arg_should_pass.erl │ ├── different_normalization_levels.erl │ ├── elixir_list_first.erl │ ├── error_in_guard.erl │ ├── fun_subtyping.erl │ ├── generator_var_shadow.erl │ ├── inner_union_subtype_of_root_union.erl │ ├── intersection_should_pass.erl │ ├── intersection_with_any.erl │ ├── list_concat_op_should_pass.erl │ ├── list_tail.erl │ ├── map_pattern_duplicate_key.erl │ ├── poly_should_pass.erl │ ├── poly_type_vars.erl │ ├── recursive_types.erl │ ├── refine_bound_var_on_mismatch.erl │ ├── refine_bound_var_with_guard_should_pass.erl │ ├── refine_comparison_should_pass.erl │ ├── refine_list_tail.erl │ └── union_fun.erl ├── misc ├── append.erl ├── int.erl ├── lambda.erl ├── lint_errors.erl ├── mfa.erl ├── singleton.erl └── undefined_errors.erl ├── property_test ├── gradualizer_erlang_abstract_code.erl ├── gradualizer_gen.erl └── gradualizer_prop.erl ├── should_fail ├── annotated_types_fail.erl ├── arg.erl ├── arith_op_fail.erl ├── arity_mismatch.erl ├── bc_fail.erl ├── bin_expression.erl ├── bin_type_error.erl ├── branch.erl ├── branch2.erl ├── call.erl ├── call_intersection_function_with_union_arg_fail.erl ├── case_pattern.erl ├── case_pattern2.erl ├── catch_expr_fail.erl ├── cons.erl ├── covariant_map_keys_fail.erl ├── cyclic_type_vars.erl ├── depth.erl ├── exhaustive.erl ├── exhaustive_float.erl ├── exhaustive_list_variants.erl ├── exhaustive_refinable_map_variants.erl ├── exhaustive_remote_user_type.erl ├── exhaustive_string_variants.erl ├── exhaustive_type.erl ├── exhaustive_user_type.erl ├── exhaustive_user_type.hrl ├── exhaustiveness_check_toggling.erl ├── generator.erl ├── guard_fail.erl ├── imported_undef.erl ├── infer_enabled.erl ├── intersection_check.erl ├── intersection_fail.erl ├── intersection_infer.erl ├── intersection_with_any_fail.erl ├── iodata_fail.erl ├── lambda_not_fun.erl ├── lc_generator_not_none_fail.erl ├── lc_not_list.erl ├── list_infer_fail.erl ├── list_op.erl ├── list_op_should_fail.erl ├── list_union_fail.erl ├── lists_map_nonempty_fail.erl ├── literal_char.erl ├── literal_patterns.erl ├── logic_op.erl ├── map_entry.erl ├── map_fail.erl ├── map_failing_expr.erl ├── map_failing_subtyping.erl ├── map_field_invalid_update.erl ├── map_literal.erl ├── map_pattern_fail.erl ├── map_refinement_fail.erl ├── map_type_error.erl ├── match.erl ├── messaging_fail.erl ├── module_info_fail.erl ├── named_fun_fail.erl ├── named_fun_infer_fail.erl ├── nil.erl ├── no_idempotent_xor.erl ├── non_neg_plus_pos_is_pos_fail.erl ├── nonempty_list_match_in_head_nonexhaustive.erl ├── nonempty_string_fail.erl ├── opaque_fail.erl ├── operator_pattern_fail.erl ├── pattern.erl ├── pattern_record_fail.erl ├── poly_fail.erl ├── poly_lists_map_fail.erl ├── poly_union_lower_bound_fail.erl ├── pp_intersection.erl ├── record.erl ├── record_exhaustive.erl ├── record_field.erl ├── record_index.erl ├── record_info_fail.erl ├── record_refinement_fail.erl ├── record_update.erl ├── record_wildcard_fail.erl ├── recursive_type_fail.erl ├── recursive_types_failing.erl ├── rel_op.erl ├── return_fun_fail.erl ├── rigid_type_variables_fail.erl ├── send_fail.erl ├── shortcut_ops_fail.erl ├── spec_and_fun_clause_intersection_fail.erl ├── string_literal.erl ├── tuple_union_arg_fail.erl ├── tuple_union_fail.erl ├── tuple_union_pattern.erl ├── tuple_union_refinement.erl ├── type_refinement_fail.erl ├── unary_op.erl ├── unary_plus_fail.erl ├── union_with_any.erl └── unreachable_after_refinement.erl ├── should_pass ├── alias_in_pattern.erl ├── andalso_any.erl ├── ann_types.erl ├── annotated_types.erl ├── any.erl ├── any_doesnt_have_type_none_pass.erl ├── any_pattern.erl ├── bc_pass.erl ├── binary_exhaustiveness_checking.erl ├── binary_in_union.erl ├── binary_literal_pattern.erl ├── bitstring.erl ├── block_scope.erl ├── bool.erl ├── bounded_funs.erl ├── call_intersection_function_with_union_arg_pass.erl ├── case.erl ├── case_of_record_with_user_defined.erl ├── catch_expr_pass.erl ├── covariant_map_keys_pass.erl ├── cyclic_otp_specs.erl ├── erlang_error_args_none_pass.erl ├── exhaustiveness_union_types.erl ├── factorial.erl ├── float.erl ├── flow.erl ├── fun_capture.erl ├── fun_spec.erl ├── guard.erl ├── guard_sequences_pass.erl ├── if_expr.erl ├── imported.erl ├── int.erl ├── intersection_pass.erl ├── intersection_with_any_pass.erl ├── iodata.erl ├── issue131.erl ├── lc.erl ├── lc_generator_not_none.erl ├── lc_var_binds_in_filters.erl ├── list.erl ├── list_concat_op_pass.erl ├── list_exhaustiveness_checking_regressions.erl ├── list_exhaustiveness_checking_regressions2.erl ├── list_exhaustiveness_checking_unreachable_clause_regression.erl ├── list_infer_pass.erl ├── list_op_pass.erl ├── listsspecs.erl ├── map.erl ├── map_as_argument_update.erl ├── map_creation.erl ├── map_field_valid_update.erl ├── map_infer_pass.erl ├── map_passing_expr.erl ├── map_passing_subtyping.erl ├── map_pattern.erl ├── map_refinement.erl ├── map_update.erl ├── map_update_with_record_field.erl ├── maybe_expr_pass.erl ├── messaging_pass.erl ├── minimised_gradualizer_fmt.erl ├── minus.erl ├── module_info.erl ├── module_info_higher_arity.erl ├── named_fun_infer_pass.erl ├── named_fun_pass.erl ├── negate_none.erl ├── nested_pattern_match.erl ├── non_neg_plus_pos_is_pos_pass.erl ├── nonempty_cons.erl ├── nonempty_list_match_in_head_exhaustive.erl ├── nonempty_string.erl ├── nonexhaustive_record_pattern.erl ├── opaque.erl ├── operator_pattern_pass.erl ├── operator_subtypes.erl ├── other_module.erl ├── pattern_bind_reuse.erl ├── pattern_record.erl ├── pattern_with_ty_vars.erl ├── poly_lists_map_constraints_pass.erl ├── poly_lists_map_pass.erl ├── poly_map_pattern.erl ├── poly_pass.erl ├── poly_pass_infer.erl ├── poly_pass_no_solve_constraints.erl ├── poly_union_lower_bound_pass.erl ├── preludes.erl ├── qlc_test.erl ├── record_info.erl ├── record_refinement.erl ├── record_union_pass.erl ├── record_union_with_any_should_pass.erl ├── record_var.erl ├── record_wildcard_pass.erl ├── record_with_user_defined.erl ├── records.erl ├── recursive_call_with_remote_union_return_type_pass.erl ├── recursive_types_passing.erl ├── refine_comparison.erl ├── refine_mismatch_using_guard_bifs.erl ├── remote_types.erl ├── remote_types_pass.erl ├── return_fun.erl ├── rigid_type_variables.erl ├── rigid_type_variables_pass.erl ├── scope.erl ├── send_pass.erl ├── sets_set.erl ├── shortcut_ops_pass.erl ├── spec_and_fun_clause_intersection_pass.erl ├── stuff_as_top.erl ├── try.erl ├── try_expr.erl ├── tuple.erl ├── tuple_union_pass.erl ├── tuple_union_pat.erl ├── tuple_union_pattern_pass.erl ├── type_decl.erl ├── type_pattern.erl ├── type_refinement_pass.erl ├── type_variable.erl ├── type_vars_term.erl ├── typed_record_field_access.erl ├── unary_negate_union_with_user_type_pass.erl ├── unary_plus.erl ├── underscore.erl ├── user_type_in_pattern_body.erl ├── user_types.erl ├── var.erl ├── var_fun.erl ├── varbind_in_block.erl ├── varbind_in_case.erl ├── varbind_in_function_head.erl ├── varbind_in_lc.erl ├── variable_binding.erl └── variable_binding_leaks.erl ├── test.erl ├── test_lib.erl ├── typechecker_tests.erl ├── typelib_tests.erl └── undefined_errors_test.erl /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "src/rebar_prv_gradualizer.erl$" 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration file 2 | # For Emacs install package `editorconfig` 3 | # For Atom install package `editorconfig` 4 | # For Sublime Text install package `EditorConfig` 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | max_line_length = 100 15 | 16 | [*.md] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.yml] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [*.json] 25 | indent_style = space 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: [] 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-20.04 13 | name: Erlang ${{matrix.otp}} 14 | strategy: 15 | matrix: 16 | otp: ['23.3.4.7', '24.3.4.4', '25.1.2'] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: erlef/setup-beam@v1 20 | with: 21 | otp-version: ${{matrix.otp}} 22 | - name: "Build" 23 | run: "make" 24 | - name: "Run tests" 25 | run: "make tests" 26 | -------------------------------------------------------------------------------- /.github/workflows/proptests.yml: -------------------------------------------------------------------------------- 1 | name: Property based tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: [] 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-20.04 13 | name: Erlang ${{matrix.otp}} 14 | strategy: 15 | matrix: 16 | otp: ['26.2.5'] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: erlef/setup-beam@v1 20 | with: 21 | otp-version: ${{matrix.otp}} 22 | - name: "Run property-based tests" 23 | run: "PROP_NUMTESTS=5000 rebar3 ct --suite test/gradualizer_prop_SUITE.erl" 24 | -------------------------------------------------------------------------------- /.github/workflows/self-check.yml: -------------------------------------------------------------------------------- 1 | name: Self check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: [] 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-20.04 13 | name: Erlang ${{matrix.otp}} 14 | strategy: 15 | matrix: 16 | otp: ['24.3.4.4'] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: erlef/setup-beam@v1 20 | with: 21 | otp-version: ${{matrix.otp}} 22 | - name: "Build" 23 | run: "make" 24 | - name: "Run self check" 25 | run: "make gradualize | tee gradualize.log" 26 | - name: "Assess self check result" 27 | run: | 28 | ERROR_LINES=$(wc -l gradualize.log | awk '{print $1}') 29 | if [ $ERROR_LINES -eq 0 ]; then 30 | echo "ok, there are no self-check errors: $ERROR_LINES == 0" 31 | exit 0 32 | else 33 | echo "we've regressed, failing the job: $ERROR_LINES != 0" 34 | exit 1 35 | fi 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.erltypes 6 | *.plt 7 | *.log 8 | *.txt 9 | logs 10 | /*.erl 11 | /.dialyzer_plt 12 | *~ 13 | erl_crash.dump 14 | ebin/* 15 | rel/example_project 16 | .concrete/DEV_MODE 17 | .rebar 18 | .erlang.mk 19 | gradualizer.d 20 | cover/ 21 | eunit.coverdata 22 | test/ct.cover.spec 23 | test/covertool.erl 24 | test/covertool.hrl 25 | /gradualizer 26 | /_build 27 | /bin/ 28 | /test_data/ 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: erlang 4 | 5 | matrix: 6 | fast_finish: true 7 | 8 | # Jobs that are allowed to fail: 9 | allow_failures: 10 | - env: TEST=".travis/travis_has_latest_otp_version" 11 | - env: TEST="make gradualize" 12 | 13 | include: 14 | 15 | # Check that the latest Erlang version is tested 16 | 17 | # This test will fail with 'X' when a new version is available via kerl. 18 | # At that point, update THIS test's otp_release to the new one. 19 | # The test will keep failing with '!', until Travis has the new version. 20 | # When the test starts failing with 'X' again, Travis has the new version. 21 | # At that point, update otp_release in the corresponding list below 22 | 23 | - env: TEST=".travis/travis_has_latest_otp_version" 24 | # Latest minor version of current OTP release 25 | otp_release: 23.1 26 | 27 | # Run make target specialized for the checks we want to make in Travis for 28 | # the latest OTP release. 29 | # Includes coverage report and dialyzer . 30 | - env: TEST="make travischeck" ENABLE_COVER=true 31 | # Latest minor version of current OTP release 32 | otp_release: 23.0 33 | 34 | - env: TEST="make gradualize" 35 | # Latest minor version of current OTP release 36 | otp_release: 23.0 37 | 38 | # OTP releases that we test in the same way. 39 | # The latest OTP version is special and therefore explicitly 40 | # included above instead. 41 | otp_release: 42 | # First minor version of current OTP release 43 | - 23.0 44 | # Last minor version of older OTP releases 45 | - 22.2 46 | - 21.3 47 | 48 | env: 49 | - TEST="make tests" 50 | 51 | script: 52 | - $TEST 53 | 54 | after_success: 55 | - .travis/after_success 56 | -------------------------------------------------------------------------------- /.travis/after_success: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$ENABLE_COVER" = true ]; then 4 | echo "Sending coverage report..." 5 | bash <(curl -s https://codecov.io/bash) -f "cover/coverage.xml" 6 | echo "Report sent!" 7 | fi 8 | -------------------------------------------------------------------------------- /.travis/travis_has_latest_otp_version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl -sO https://raw.githubusercontent.com/kerl/kerl/master/kerl 4 | chmod a+x kerl 5 | LATEST=`./kerl update releases | tr ' ' '\n' | tail -n1` 6 | rm ./kerl 7 | 8 | if [ "$LATEST" != "$TRAVIS_OTP_RELEASE" ]; then 9 | printf "\nA newer OTP release is available via kerl ($LATEST)!\n" 10 | printf " (.travis.yml uses $TRAVIS_OTP_RELEASE for this test)\n\n" 11 | printf "See .travis.yml for instructions about how to fix this.\n" 12 | false 13 | fi 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Radek Szymczyszyn 4 | Copyright (c) 2017-2019 Josef Svenningsson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /doc/examples.md: -------------------------------------------------------------------------------- 1 | # Some examples of Gradualizer in action 2 | 3 | ![An example Gradualizer exhaustiveness checking diagnostic](screenshots/exhaustive_type.png) 4 | 5 | Here's the same example in text for copy-pasting: 6 | 7 | ```erlang 8 | 1 -module(exhaustive_type). 9 | 2 10 | 3 -export([allergen_score/1]). 11 | 4 12 | 5 -type allergen() :: eggs 13 | 6 | chocolate 14 | 7 | pollen 15 | 8 | cats. 16 | 9 17 | 10 -spec allergen_score(allergen()) -> integer(). 18 | 11 allergen_score(Al) -> 19 | 12 case Al of 20 | 13 eggs -> 1; 21 | 14 chocolate -> 32; 22 | 15 pollen -> 64 23 | 16 end. 24 | ``` 25 | 26 | ``` 27 | $ gradualizer test/should_fail/exhaustive_type.erl 28 | test/should_fail/exhaustive_type.erl: Nonexhaustive patterns on line 13 at column 9 29 | Example values which are not covered: 30 | cats 31 | ``` 32 | 33 | [ErlangLS](https://github.com/erlang-ls/erlang_ls) integration example: 34 | 35 | ![Gradualizer diagnostics with ErlangLS](screenshots/134913123-4700b299-2714-4227-b7e2-f8c816b138a5.gif) 36 | -------------------------------------------------------------------------------- /doc/screenshots/134913123-4700b299-2714-4227-b7e2-f8c816b138a5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefs/Gradualizer/112646d85d0cc20364a56184ab30fb8d63a4a63f/doc/screenshots/134913123-4700b299-2714-4227-b7e2-f8c816b138a5.gif -------------------------------------------------------------------------------- /doc/screenshots/exhaustive_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josefs/Gradualizer/112646d85d0cc20364a56184ab30fb8d63a4a63f/doc/screenshots/exhaustive_type.png -------------------------------------------------------------------------------- /erlang_ls.config: -------------------------------------------------------------------------------- 1 | apps_dirs: 2 | - "." 3 | - "_build/default/lib/*" 4 | code_path_extra_dirs: 5 | - "test" 6 | - "test_data" 7 | - "_build/test/lib/gradualizer/test/property_test" 8 | include_dirs: 9 | - "include" 10 | - "src" 11 | - "_build/default/lib/" 12 | diagnostics: 13 | enabled: 14 | - compiler 15 | - gradualizer 16 | disabled: 17 | - elvis 18 | - dialyzer 19 | -------------------------------------------------------------------------------- /examples/rebar3/README.md: -------------------------------------------------------------------------------- 1 | # Gradualizer plugin for rebar3 2 | 3 | To run Gradualizer from rebar3, add it as a plugin in your `rebar.config`: 4 | ```Erlang 5 | {plugins, [ 6 | {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {branch, "master"}}} 7 | ]}. 8 | ``` 9 | 10 | # Analyzing erl files or beam files 11 | 12 | Gradualizer can process both `*.erl` files and `*.beam` files. 13 | 14 | By default, `rebar3 gradualizer` processes `*.erl` files from source directories. 15 | In this default mode gradualizer tries its best to get include directories, header files 16 | and compiler options right. However, in case of complex setup gradualizer may miss something. 17 | 18 | `rebar3 gradualizer --use_beams` uses `*.beam` files from `ebin` directories as input 19 | (beam files should be produced with debug info to be analyzable). 20 | 21 | `--use_beams` may be a more robust choice if there are problems with analyzing `*.erl` files. 22 | 23 | # Options (`gradualizer_opts`) 24 | 25 | Gradualizer checks every source file in your app(s), unless configured not to. 26 | Configuration is read from `gradualizer_opts` in `rebar.config` which 27 | should be a `proplists:proplist()`. 28 | The following options are supported: 29 | 30 | ## include 31 | 32 | type: `[filelib Wildcard]` 33 | 34 | Files to type check - Gradualizer includes every `.erl` file in the source directory if undefined 35 | 36 | ## exclude 37 | 38 | type: `[filelib Wildcard]` 39 | 40 | Files to not type check. Subtracts the list of included files 41 | 42 | ## stop_on_first_error 43 | 44 | type: `boolean()` 45 | 46 | if 'true' stop type checking at the first error, if 'false' continue checking all functions in the given file and all files in the given directory 47 | 48 | ## apps 49 | 50 | types: `string()` 51 | 52 | Apps to type check. In the case of umbrella projects, it would only run the type check on the list of apps defined. The list should be a comma separated list. 53 | 54 | For example: 55 | ``` 56 | rebar3 gradualizer --apps=app1,app2 57 | ``` 58 | 59 | It can also be defined in `rebar.config`. Note that the list of apps defined in this file will only be used if no other app is passed to the `rebar3 gradualizer` command. 60 | ``` 61 | {gradualizer_opts, [ 62 | {apps, [ 63 | app1, 64 | app2 65 | ]} 66 | ]}. 67 | ``` 68 | -------------------------------------------------------------------------------- /examples/rebar3/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. 3 | {plugins, [ 4 | {gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {branch, "master"}}} 5 | ]}. 6 | 7 | {gradualizer_opts, [ 8 | {exclude, ["src/not_*.erl"]} 9 | ]}. 10 | -------------------------------------------------------------------------------- /examples/rebar3/src/not_included.erl: -------------------------------------------------------------------------------- 1 | -module(not_included). 2 | 3 | -compile([export_all]). 4 | 5 | -spec c(boolean()) -> integer(). 6 | c(X) -> 7 | X. 8 | -------------------------------------------------------------------------------- /examples/rebar3/src/rebar3_example.app.src: -------------------------------------------------------------------------------- 1 | {application, rebar3_example, 2 | [{description, "An example rebar3 project"}, 3 | {vsn, "1.0.0"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib 8 | ]}, 9 | {env,[]}, 10 | {modules, []} 11 | ]}. 12 | -------------------------------------------------------------------------------- /examples/rebar3/src/rebar3_example.erl: -------------------------------------------------------------------------------- 1 | -module(rebar3_example). 2 | 3 | -compile([export_all]). 4 | 5 | -spec c(boolean()) -> boolean(). 6 | c(X) -> 7 | X. 8 | -------------------------------------------------------------------------------- /gradualize-ignore.lst: -------------------------------------------------------------------------------- 1 | ebin/gradualizer_file_utils.beam: Call to undefined function epp:open/5 on line 66 at column 16 2 | ebin/rebar_prv_gradualizer.beam: Undefined remote type rebar_state:t/0 on line 23 at column 11 3 | ebin/rebar_prv_gradualizer.beam: Undefined remote type rebar_state:t/0 on line 40 at column 9 4 | ebin/rebar_prv_gradualizer.beam: Undefined remote type rebar_app_info:t/0 on line 58 at column 28 5 | ebin/rebar_prv_gradualizer.beam: Undefined remote type rebar_app_info:t/0 on line 76 at column 21 6 | ebin/rebar_prv_gradualizer.beam: Call to undefined function rebar_dir:src_dirs/2 on line 111 at column 24 7 | ebin/rebar_prv_gradualizer.beam: Call to undefined function rebar_dir:src_dirs/2 on line 119 at column 24 8 | ebin/rebar_prv_gradualizer.beam: Call to undefined function rebar_state:get/3 on line 133 at column 25 9 | ebin/rebar_prv_gradualizer.beam: Call to undefined function rebar_state:project_apps/1 on line 148 at column 34 10 | -------------------------------------------------------------------------------- /include/gradualizer.hrl: -------------------------------------------------------------------------------- 1 | %% Syntax elements for type annotations 2 | %% 3 | %% The macro ?annotate_type/2 can be used to annotate an expression with a 4 | %% type. This is useful to add type annotation after fetching a value from an 5 | %% ets table, received a message on a known form or read from a dict, proplist, 6 | %% process dictionary, etc. 7 | %% 8 | %% The specified type must be compatible with the type detected by Gradualizer. 9 | %% Otherwise a type error is reported. 10 | %% 11 | %% N = ?annotate_type( Message, non_neg_integer() ) 12 | %% 13 | %% The macro ?assert_type/2 can be used to refine (downcast) a type propagated 14 | %% by Gradualizer. For example, the programmer may know that the length of a 15 | %% list is within a certain range, rather than any non_neg_integer(): 16 | %% 17 | %% Arity = ?assert_type( length(Args), arity() ) 18 | %% 19 | %% The functions '::'/2 and ':::'/2 can also be used directly if the type is 20 | %% quoted: 21 | %% 22 | %% N = '::'(Message, "non_neg_integer()") 23 | %% 24 | %% Gradualizer detects occurrences of the functions '::'/2 and ':::'/2 and 25 | %% adjusts type checking accordingly. The macros are supplied only for 26 | %% convenience. 27 | %% 28 | -compile({inline, ['::'/2, ':::'/2]}). 29 | -compile({nowarn_unused_function, ['::'/2, ':::'/2]}). 30 | -ignore_xref(['::'/2, ':::'/2]). 31 | 32 | -spec '::'(any(), any()) -> any(). 33 | '::'(Expr, _Type) -> Expr. 34 | 35 | -spec ':::'(any(), any()) -> any(). 36 | ':::'(Expr, _Type) -> Expr. 37 | 38 | %% Type annotation 39 | -define(annotate_type(Expr, Type), '::'(Expr, ??Type)). 40 | 41 | %% Refinement (downcast) AKA type assertion 42 | -define(assert_type(Expr, Type), ':::'(Expr, ??Type)). 43 | -------------------------------------------------------------------------------- /priv/extra_specs/epp.specs.erl: -------------------------------------------------------------------------------- 1 | -module(epp). 2 | 3 | %% `epp:open/1' is available at least since OTP-17, 4 | %% so we're not overriding its spec here, 5 | %% as its unlikely Gradualizer is that much backwards compatible. 6 | 7 | %% `epp:open/5' was removed in OTP-24: 8 | %% https://github.com/erlang/otp/commit/5281a8c7f77d45a3c36fca9c1a2e4d3812f6fc3d#diff-580a349c49b1d9b5415166e18f5279728d934efe0cebc4ee5a87823055ec3413 9 | -spec open(_, _, _, _, _) -> any(). 10 | -------------------------------------------------------------------------------- /priv/prelude/README: -------------------------------------------------------------------------------- 1 | This directory is the Gradualizer prelude. It is used to define and override 2 | types and specs in modules from OTP. 3 | 4 | A common correction is overriding a spec such as foo(term()) -> term() with 5 | a polymorphic one such as foo(A) -> A. 6 | 7 | For more information about the gradual type system, see the manual. 8 | -------------------------------------------------------------------------------- /priv/prelude/erlang.specs.erl: -------------------------------------------------------------------------------- 1 | -module(erlang). 2 | 3 | %% This module contains specs to replace incorrect or inexact specs in OTP. 4 | 5 | %% The commented-out specs are the original specs from OTP 21 (erts-10.2.4) with 6 | %% type variebles unfolded if they occur only once in the spec. 7 | 8 | %% -spec hd([term(), ...]) -> term(). 9 | -spec hd([A, ...]) -> A. 10 | 11 | %% -spec min(term(), term()) -> term(). 12 | %% -spec max(term(), term()) -> term(). 13 | -spec max(A, B) -> A | B. 14 | -spec min(A, B) -> A | B. 15 | 16 | %% -spec tl([term(), ...]) -> [term()]. 17 | -spec tl([A, ...]) -> [A]. 18 | 19 | -spec erlang:'--'(list(), list()) -> list(). 20 | 21 | %% Prior to OTP 24.1 the spec does not list `none' as valid `Args', 22 | %% but the function accepts it and works properly. 23 | -spec erlang:error(Reason, Args) -> no_return() when 24 | Reason :: term(), 25 | Args :: [term()] | none. 26 | 27 | %% `erlang:error/3' was introduced in OTP 24, but it's spec is also fixed only since OTP 24.1. 28 | -spec erlang:error(Reason, Args, Options) -> no_return() when 29 | Reason :: term(), 30 | Args :: [term()] | none, 31 | Options :: [Option], 32 | Option :: {'error_info', ErrorInfoMap}, 33 | ErrorInfoMap :: #{'cause' => term(), 34 | 'module' => module(), 35 | 'function' => atom()}. 36 | -------------------------------------------------------------------------------- /priv/prelude/filename.specs.erl: -------------------------------------------------------------------------------- 1 | -module(filename). 2 | 3 | -type deep_list() :: lists:deep_list(char() | atom()). 4 | 5 | -spec basename(binary()) -> binary(); 6 | (string() | atom() | deep_list()) -> string(). 7 | 8 | -spec dirname(binary()) -> binary(); 9 | (string() | atom() | deep_list()) -> string(). 10 | 11 | -spec rootname(binary()) -> binary(); 12 | (string() | atom() | deep_list()) -> string(). 13 | 14 | -type name() :: string() | atom() | deep_list(). 15 | 16 | -spec join([name()]) -> string(); 17 | ([binary()]) -> binary(). 18 | 19 | -spec join(name(), name()) -> string(); 20 | (binary(), name()) -> binary(); 21 | (name(), binary()) -> binary(); 22 | (binary(), binary()) -> binary(). 23 | -------------------------------------------------------------------------------- /priv/prelude/lists.specs.erl: -------------------------------------------------------------------------------- 1 | -module(lists). 2 | 3 | %% This module contains specs to replace incorrect or inexact specs in OTP. The 4 | %% commented-out specs are the original specs given in OTP 21 (stdlib-3.7.1) 5 | 6 | -export_type([deep_list/1]). 7 | 8 | -type deep_list(A) :: [A | deep_list(A)]. 9 | 10 | %% flatten/1,2 are defined using recursive constraints, which is something we 11 | %% don't handle yet. We use a recursive user-defined type deep_list/1 instead. 12 | 13 | %% -spec flatten(DeepList) -> List when 14 | %% DeepList :: [term() | DeepList], 15 | %% List :: [term()]. 16 | -spec flatten(deep_list(A)) -> [A]. 17 | 18 | %% -spec flatten(DeepList, Tail) -> List when 19 | %% DeepList :: [term() | DeepList], 20 | %% Tail :: [term()], 21 | %% List :: [term()]. 22 | -spec flatten(deep_list(A), [A]) -> [A]. 23 | 24 | %% The original fold{l,r} and mapfold{l,r} functions don't require that Acc is 25 | %% the same type throughout the procedure. We do. 26 | 27 | %% -spec foldl(Fun, Acc0, List) -> Acc1 when 28 | %% Fun :: fun((Elem :: T, AccIn) -> AccOut), 29 | %% Acc0 :: term(), 30 | %% Acc1 :: term(), 31 | %% AccIn :: term(), 32 | %% AccOut :: term(), 33 | %% List :: [T], 34 | %% T :: term(). 35 | -spec foldl(fun((T, Acc) -> Acc), Acc, [T]) -> Acc. 36 | -spec foldr(fun((T, Acc) -> Acc), Acc, [T]) -> Acc. 37 | 38 | %% -spec mapfoldl(Fun, Acc0, List1) -> {List2, Acc1} when 39 | %% Fun :: fun((A, AccIn) -> {B, AccOut}), 40 | %% Acc0 :: term(), 41 | %% Acc1 :: term(), 42 | %% AccIn :: term(), 43 | %% AccOut :: term(), 44 | %% List1 :: [A], 45 | %% List2 :: [B], 46 | %% A :: term(), 47 | %% B :: term(). 48 | -spec mapfoldl(fun((A, Acc) -> {B, Acc}), Acc, [A]) -> {[B], Acc}. 49 | -spec mapfoldr(fun((A, Acc) -> {B, Acc}), Acc, [A]) -> {[B], Acc}. 50 | -------------------------------------------------------------------------------- /priv/test/undefined_errors_helper.erl: -------------------------------------------------------------------------------- 1 | -module(undefined_errors_helper). 2 | -export([und_rec/0, und_ty/0, not_exp_ty/0]). 3 | -export_type([j/0, 4 | expands_to_undefined_remote/0, 5 | expands_to_struct_with_undefined_remote/0, 6 | expands_to_struct_with_undefined_local/0, 7 | expands_to_struct_with_undefined_record/0]). 8 | 9 | -type j() :: undefined_type1(). 10 | -type not_exported() :: ok. 11 | -type expands_to_undefined_remote() :: undefined_errors:undefined_type2(). 12 | -type expands_to_struct_with_undefined_remote() :: {struct, undefined_errors:undefined_type3()}. 13 | -type expands_to_struct_with_undefined_local() :: {struct, undefined_type4()}. 14 | -type expands_to_struct_with_undefined_record() :: {struct, #undefined_record1{}}. 15 | 16 | -spec und_rec() -> #undefined_record2{}. 17 | und_rec() -> ok. 18 | 19 | -spec und_ty() -> undefined_errors:undefined_type5(). 20 | und_ty() -> ok. 21 | 22 | -spec not_exp_ty() -> not_exported(). 23 | not_exp_ty() -> ok. 24 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {profiles, 2 | [ 3 | {test, [ 4 | {deps, 5 | [ 6 | {proper, {git, "https://github.com/proper-testing/proper.git", {branch, "master"}}} 7 | ]}, 8 | %% see the maybe expression fail; 9 | %% the VM also needs to be configured to load the module 10 | {erl_opts, [{feature,maybe_expr,enable}]} 11 | ]} 12 | ]}. 13 | 14 | {escript_emu_args, "%%! -escript main gradualizer_cli\n"}. 15 | 16 | {shell, [{apps, [gradualizer]}]}. 17 | 18 | {project_plugins, [rebar3_ex_doc]}. 19 | 20 | {ex_doc, [ 21 | {extras, [ 22 | {'README.md', #{title => <<"Overview">>}}, 23 | {'LICENSE', #{title => <<"License">>}} 24 | ]}, 25 | {main, <<"readme">>}, 26 | {source_url, <<"https://github.com/josefs/Gradualizer">>} 27 | ]}. 28 | 29 | {hex, [{doc, ex_doc}]}. 30 | -------------------------------------------------------------------------------- /src/constraints.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(__CONSTRAINTS_HRL__). 2 | -define(__CONSTRAINTS_HRL__, true). 3 | 4 | -record(constraints, { lower_bounds = #{} :: #{ constraints:var() => 5 | gradualizer_type:abstract_type() }, 6 | upper_bounds = #{} :: #{ constraints:var() => 7 | gradualizer_type:abstract_type() } }). 8 | 9 | -endif. %% __CONSTRAINTS_HRL__ 10 | -------------------------------------------------------------------------------- /src/gradualizer.app.src: -------------------------------------------------------------------------------- 1 | {application, gradualizer, 2 | [{description, "A type checker for Erlang"}, 3 | {vsn, "git"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib, 8 | syntax_tools 9 | ]}, 10 | {licenses, ["MIT License"]}, 11 | {mod, {gradualizer_app, []}}, 12 | {env,[ 13 | {providers, [rebar_prv_gradualizer]} 14 | ]}, 15 | {modules, []} 16 | ]}. 17 | -------------------------------------------------------------------------------- /src/gradualizer_app.erl: -------------------------------------------------------------------------------- 1 | %% @private 2 | -module(gradualizer_app). 3 | 4 | -behaviour(application). 5 | 6 | %% Application callbacks 7 | -export([start/2, 8 | stop/1 9 | ]). 10 | 11 | %%%=================================================================== 12 | %%% Application callbacks 13 | %%%=================================================================== 14 | 15 | start(_StartType, _StartArgs) -> 16 | Opts = application:get_env(gradualizer, options, []), 17 | gradualizer_sup:start_link(Opts). 18 | 19 | stop(_State) -> 20 | ok. 21 | -------------------------------------------------------------------------------- /src/gradualizer_prelude.erl: -------------------------------------------------------------------------------- 1 | %% @private 2 | -module(gradualizer_prelude). 3 | 4 | -compile({parse_transform, gradualizer_prelude_parse_trans}). 5 | 6 | -export([get_modules_and_forms/0]). 7 | 8 | -spec get_modules_and_forms() -> [{module(), gradualizer_file_utils:abstract_forms()}]. 9 | get_modules_and_forms() -> 10 | error(undef). % function body replaced by the parse transform 11 | -------------------------------------------------------------------------------- /src/gradualizer_sup.erl: -------------------------------------------------------------------------------- 1 | %% @private 2 | %% @doc Main Gradualizer supervisor 3 | -module(gradualizer_sup). 4 | 5 | -behaviour(supervisor). 6 | 7 | %% API 8 | -export([start_link/1]). 9 | 10 | %% Supervisor callbacks 11 | -export([init/1]). 12 | 13 | -define(SERVER, ?MODULE). 14 | 15 | %%=================================================================== 16 | %% API functions 17 | %%=================================================================== 18 | 19 | %% @doc Start the supervisor 20 | -spec start_link(Opts) -> R when 21 | Opts :: list(), 22 | R :: {ok, Pid :: pid()} 23 | | {error, {already_started, Pid :: pid()}} 24 | | {error, {shutdown, term()}} 25 | | {error, term()} 26 | | ignore. 27 | start_link(Opts) -> 28 | supervisor:start_link({local, ?SERVER}, ?MODULE, [Opts]). 29 | 30 | %%=================================================================== 31 | %% Supervisor callbacks 32 | %%=================================================================== 33 | 34 | -spec init(Args :: term()) -> 35 | {ok, {SupFlags :: supervisor:sup_flags(), 36 | [ChildSpec :: supervisor:child_spec()]}} | 37 | ignore. 38 | init([Opts]) -> 39 | SupFlags = #{strategy => one_for_one, 40 | intensity => 1, 41 | period => 5}, 42 | Children = [child(gradualizer_db, Opts), 43 | child(gradualizer_cache, Opts)], 44 | {ok, {SupFlags, Children}}. 45 | 46 | %%=================================================================== 47 | %% Internal functions 48 | %%=================================================================== 49 | 50 | child(Module, Opts) -> 51 | #{id => Module, 52 | start => {Module, start_link, [Opts]}, 53 | restart => permanent, 54 | shutdown => 5000, 55 | type => worker, 56 | modules => [Module]}. 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/typechecker.hrl: -------------------------------------------------------------------------------- 1 | -ifndef(__TYPECHECKER_HRL__). 2 | -define(__TYPECHECKER_HRL__, true). 3 | 4 | -record(clauses_controls, {exhaust}). 5 | 6 | -record(env, {fenv = #{} :: #{{atom(), arity()} => 7 | [gradualizer_type:af_constrained_function_type()] 8 | | [gradualizer_type:gr_any_type()] 9 | }, 10 | imported = #{} :: #{{atom(), arity()} => module()}, 11 | venv = #{} :: typechecker:venv(), 12 | tenv :: gradualizer_lib:tenv(), 13 | verbose = false :: boolean(), 14 | exhaust = true :: boolean(), 15 | %% Controls driving the type checking algorithm 16 | %% per clauses-list (fun/case/try-catch/receive). 17 | clauses_stack = [] :: [#clauses_controls{}], 18 | %% Performance hack: Unions larger than this limit are replaced by any() in normalization. 19 | union_size_limit :: non_neg_integer(), 20 | current_spec = none :: erl_parse:abstract_form() | none, 21 | solve_constraints = false :: boolean(), 22 | %% Skip functions whose guards are too complex to be handled yet, 23 | %% which might result in false positives when checking rest of the functions 24 | no_skip_complex_guards = false :: boolean() 25 | }). 26 | 27 | -endif. %% __TYPECHECKER_HRL__ 28 | -------------------------------------------------------------------------------- /src/typelib.hrl: -------------------------------------------------------------------------------- 1 | %% convenience guards 2 | 3 | %% same as typechecker:is_int_type/2 but can be used as a guard 4 | -define(is_int_type(T), 5 | (tuple_size(T) =:= 4 andalso 6 | element(1, T) =:= type andalso 7 | (element(3, T) =:= integer orelse 8 | element(3, T) =:= pos_integer orelse 9 | element(3, T) =:= neg_integer orelse 10 | element(3, T) =:= non_neg_integer orelse 11 | element(3, T) =:= range)) 12 | orelse 13 | (tuple_size(T) =:= 3 andalso 14 | (element(1, T) =:= integer orelse 15 | element(1, T) =:= char))). 16 | 17 | %% same as typechecker:is_list_type/1 but can be used as a guard 18 | -define(is_list_type(T), 19 | (tuple_size(T) =:= 4 andalso 20 | element(1, T) =:= type andalso 21 | (element(3, T) =:= list orelse 22 | element(3, T) =:= nil orelse 23 | element(3, T) =:= nonempty_list orelse 24 | element(3, T) =:= maybe_improper_list orelse 25 | element(3, T) =:= nonempty_improper_list))). 26 | 27 | %% Checks if an operator is a comparison operator 28 | -define(is_comp_op(OP), 29 | (OP =:= '>' orelse OP =:= '>=' orelse 30 | OP =:= '<' orelse OP =:= '=<' orelse 31 | OP =:= '==' orelse OP =:= '/=' orelse 32 | OP =:= '=:=' orelse OP =:= '=/=')). 33 | -------------------------------------------------------------------------------- /src/user_default.erl: -------------------------------------------------------------------------------- 1 | %% @doc 2 | %% Example `user_default' file to extend your Erlang shell with Gradualizer features. 3 | %% @end 4 | -module(user_default). 5 | 6 | -export([c/1]). 7 | 8 | %% @doc Type check and compile file. 9 | %% 10 | %% @see //stdlib/shell_default:c/1 11 | c(File) when not is_atom(File) -> 12 | % TODO: we can be a lot more clever about recognizing 13 | % if the argument is a file or a module, just like 14 | % shell_default:c/1. 15 | case gradualizer:type_check_file(File) of 16 | ok -> 17 | shell_default:c(File); 18 | Err -> 19 | Err 20 | end; 21 | c(File) -> 22 | shell_default:c(File). 23 | 24 | -------------------------------------------------------------------------------- /talks/Makefile: -------------------------------------------------------------------------------- 1 | talk.pdf: talk.tex 2 | pdflatex talk.tex 3 | 4 | talk.tex: talk.md Makefile 5 | pandoc talk.md -s -t beamer -o talk.tex --slide-level=2 --tab-stop=2 --highlight-style=zenburn 6 | -------------------------------------------------------------------------------- /talks/abstract.txt: -------------------------------------------------------------------------------- 1 | Title: 2 | 3 | A Gradual Type system for Erlang 4 | 5 | abstract 6 | 7 | This talk introduces a new type system for Erlang based on Gradual Typing. The principles of Gradual Typing has emerged in the type system research community over the last decade and has among other things resulted in TypeScript, a typed dialect of JavaScript. Gradual Typing is tailored to mix static and dynamic code. 8 | 9 | The type system provides pay-as-you-go static checking: the more type 10 | annotation in the program, the more static checking will be performed. 11 | 12 | The tool we've developed uses Erlang's current syntax for specs, and it 13 | works on existing code bases without change. 14 | 15 | Dialyzer is the current most popular tool for static checking of 16 | Erlang. Our gradual type system turns out to be somewhat complementary 17 | to the Dialyzer. While the Dialyzer aims to give no false positives, our type system always reports an error whenever a type spec doesn't match the code. We'll provide an in-depth comparison of the two 18 | tools. 19 | 20 | 21 | Objective: 22 | 23 | Present our new type checking tool for Erlang and show how to 24 | effectively use it in a code base. 25 | 26 | 27 | Audience: 28 | 29 | All Erlang developers 30 | 31 | Biography: 32 | 33 | Josef Svenningsson is a System Manager at Ericsson working on one of 34 | the the oldest, active Erlang codebases: the SGSN-MME. 35 | In a previous life he was an Assistant Professor at Chalmers University of Technology, with a broad range of interest in computer science. He has published papers on wide variety of topics, including: program analysis, constraint solving, security, programming language design, testing and high-performance computing. 36 | 37 | -------------------------------------------------------------------------------- /test/absform_tests.erl: -------------------------------------------------------------------------------- 1 | -module(absform_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | -include("typechecker.hrl"). 5 | 6 | function_type_list_to_fun_types_test() -> 7 | {attribute, _, spec, {_, FunTypeList}} = 8 | merl:quote("-spec f(T)-> boolean() when T :: tuple();" 9 | " (atom()) -> any()."), 10 | FunTypeListNoPos = lists:map(fun typelib:remove_pos/1, FunTypeList), 11 | BoundedFunTypeList = absform:normalize_function_type_list(FunTypeListNoPos), 12 | Env = test_lib:create_env([]), 13 | Ty = typechecker:bounded_type_list_to_type(Env, BoundedFunTypeList), 14 | ?assertMatch({type,0,'fun', 15 | [ 16 | {type,0,product,[ 17 | {type, 0, union, [{type, 0, atom, []}, {type, 0, tuple, any}]} 18 | ]}, 19 | {type, 0, union, [ 20 | {type, 0, any, []}, 21 | {type, 0, union, [{atom, 0, false}, {atom, 0, true}]} 22 | ]} 23 | ]}, Ty), 24 | ok. 25 | 26 | extract_function_from_call_test() -> 27 | CallExpr = merl:quote("add(5, Pi)"), 28 | FunExpr = merl:quote("fun add/2"), 29 | {'fun', _, ExpectedFunction} = FunExpr, 30 | Function = absform:extract_function_from_call(CallExpr), 31 | ?assertEqual(ExpectedFunction, Function). 32 | 33 | extract_function_from_call_remote_test() -> 34 | CallExpr = merl:quote("nums:add(5, Pi)"), 35 | FunExpr = merl:quote("fun nums:add/2"), 36 | {'fun', _, ExpectedFunction} = FunExpr, 37 | Function = absform:extract_function_from_call(CallExpr), 38 | ?assertEqual(ExpectedFunction, Function). 39 | -------------------------------------------------------------------------------- /test/check_name_clashes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #set -x 4 | 5 | if [ x$(uname) == x"Darwin" ]; then 6 | BASENAME=basename 7 | else 8 | BASENAME="basename -a" 9 | fi 10 | 11 | UNIQ=$(find test/should_pass test/should_fail test/known_problems/*/ -name \*.erl \ 12 | | xargs $BASENAME \ 13 | | sort \ 14 | | uniq -c \ 15 | | sort -n \ 16 | | tail -1) 17 | 18 | COUNT=$(echo $UNIQ | awk '{print $1}') 19 | 20 | if [ "$COUNT" == 1 ]; then 21 | exit 0 22 | else 23 | echo "Name clash in tests:" 24 | find test -name $(echo $UNIQ | awk '{print $2}') 25 | exit 1 26 | fi 27 | -------------------------------------------------------------------------------- /test/dir/test_in_dir.erl: -------------------------------------------------------------------------------- 1 | -module(test_in_dir). 2 | 3 | -export([fail/1]). 4 | 5 | -spec fail(integer()) -> atom(). 6 | fail(N) -> N. 7 | -------------------------------------------------------------------------------- /test/gradualizer_db_tests.erl: -------------------------------------------------------------------------------- 1 | -module(gradualizer_db_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | import_module_test_() -> 6 | {setup, 7 | fun setup_app/0, 8 | fun cleanup_app/1, 9 | [?_assertMatch(not_found, gradualizer_db:import_module(hello)), 10 | ?_assertMatch(ok, gradualizer_db:import_module(lists)) 11 | ]}. 12 | 13 | %% 14 | %% Helper functions 15 | %% 16 | 17 | setup_app() -> 18 | {ok, Apps} = application:ensure_all_started(gradualizer), 19 | Apps. 20 | 21 | cleanup_app(Apps) -> 22 | [ok = application:stop(App) || App <- Apps], 23 | ok. 24 | -------------------------------------------------------------------------------- /test/gradualizer_highlight_tests.erl: -------------------------------------------------------------------------------- 1 | -module(gradualizer_highlight_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | prettyprint_and_highlight_test() -> 6 | Expr = {op,1,'andalso',{var,1,'X'},{op,1,'not',{var,1,'X'}}}, 7 | Forms = [{attribute,1,spec, 8 | {{f,1}, 9 | [{type,1,'fun', 10 | [{type,1,product,[{type,1,integer,[]}]},{atom,1,ok}]}]}}, 11 | {function,1,f,1, 12 | [{clause,1, 13 | [{var,1,'X'}], 14 | [], 15 | [Expr]}]}], 16 | Pretty = gradualizer_highlight:prettyprint_and_highlight(Expr, Forms, _Color = false), 17 | ?assertEqual("-spec f(integer()) -> ok.\n" 18 | "f(X) -> X andalso not X.\n" 19 | " ^^^^^^^^^^^^^^^\n", 20 | lists:flatten(Pretty)). 21 | 22 | %% Tests the special form {clauses, [...]} which doesn't have any annotation. 23 | prettyprint_and_highlight_fun_test() -> 24 | Expr = {atom, 1, foo}, 25 | Forms = [{function, 1, f, 0, 26 | [{clause, 1, [], [], 27 | [{'fun', 1, {clauses, [{clause, 1, [], [], 28 | [Expr]}]}}]}]}], 29 | Pretty = gradualizer_highlight:prettyprint_and_highlight(Expr, Forms, _Color = false), 30 | ?assertEqual("f() -> fun () -> foo end.\n" 31 | " ^^^\n", 32 | lists:flatten(Pretty)). 33 | -------------------------------------------------------------------------------- /test/gradualizer_int_tests.erl: -------------------------------------------------------------------------------- 1 | -module(gradualizer_int_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | int_range_diff_test_() -> 6 | [ 7 | ?_assertEqual([{1, 2}, {6, 10}], 8 | gradualizer_int:int_range_diff({1, 10}, {3, 5})), 9 | ?_assertEqual([{neg_inf, -11}], 10 | gradualizer_int:int_range_diff({neg_inf, -1}, {-10, pos_inf})), 11 | ?_assertEqual([{neg_inf, -11}], 12 | gradualizer_int:int_range_diff({neg_inf, pos_inf}, {-10, pos_inf})), 13 | ?_assertEqual([{10, pos_inf}], 14 | gradualizer_int:int_range_diff({neg_inf, pos_inf}, {neg_inf, 9})), 15 | ?_assertEqual([{10, pos_inf}], 16 | gradualizer_int:int_range_diff({0, pos_inf}, {neg_inf, 9})), 17 | ?_assertEqual([], 18 | gradualizer_int:int_range_diff({1, 10}, {neg_inf, pos_inf})), 19 | ?_assertEqual([{1, 10}], 20 | gradualizer_int:int_range_diff({1, 10}, {-10, -1})), 21 | ?_assertEqual([{2, 2}], 22 | gradualizer_int:int_range_diff({1, 2}, {1, 1})), 23 | ?_assertEqual([{1, 1}], 24 | gradualizer_int:int_range_diff({1, 2}, {2, 2})) 25 | ]. 26 | -------------------------------------------------------------------------------- /test/known_problems/README.md: -------------------------------------------------------------------------------- 1 | Known problems 2 | ============== 3 | 4 | The `should_pass` directory contains modules without type errors but for which 5 | type errors are reported. 6 | 7 | The `should_fail` directory contains modules for which we expect type errors to 8 | be reported, but which currently pass without errors. -------------------------------------------------------------------------------- /test/known_problems/should_fail/README.md: -------------------------------------------------------------------------------- 1 | Add modules here that are meant to not pass typechecking but at the moment they 2 | either crash Gradualizer or happen to pass typechecking. 3 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/arith_op.erl: -------------------------------------------------------------------------------- 1 | -module(arith_op). 2 | -export([int_error/2]). 3 | 4 | -spec int_error(any(), float()) -> integer(). 5 | int_error(X, Y) -> 6 | A = X div Y, 7 | A. 8 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/binary_comprehension.erl: -------------------------------------------------------------------------------- 1 | -module(binary_comprehension). 2 | -compile([debug_info]). 3 | -export([ 4 | bitstring_match/0 5 | ]). 6 | 7 | -spec bitstring_match() -> <<_:7, _:_*3>> | string(). 8 | bitstring_match() -> 9 | << <<1:N>> || N <- lists:seq(7, 12)>>. 10 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/case_pattern_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(case_pattern_should_fail). 2 | -export([f/2]). 3 | 4 | %% Expected error: The variable X is expected to have type atom() but it has type integer(). 5 | %% It doesn't throw any error because type_check_expr doesn't check patterns in case expressions 6 | %% as type_check_expr_in does. 7 | -spec f(integer(), atom()) -> ok. 8 | f(X, Y) -> 9 | case Y of 10 | X -> anything 11 | end, 12 | ok. -------------------------------------------------------------------------------- /test/known_problems/should_fail/exhaustive_argumentwise.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_argumentwise). 2 | 3 | -export([f/2]). 4 | 5 | -type t() :: ala | ola. 6 | 7 | -spec f(t(), any()) -> ok. 8 | f(ala, _) -> ok. 9 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/exhaustive_expr.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_expr). 2 | 3 | -export([f/1]). 4 | 5 | %% Expected error: Nonexhaustive patterns, example values which are not covered: b. 6 | %% It doesn't throw any error because type_check_expr doesn't do any exhaustiveness checking. 7 | -spec f(a | b) -> ok. 8 | f(X) -> 9 | case X of 10 | a -> anything 11 | end, 12 | ok. 13 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/exhaustive_map_variants.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_map_variants). 2 | 3 | %% This extends cases from test/should_fail/exhaustive_user_type.erl to variants defined as maps. 4 | 5 | -export([map_variants/1]). 6 | 7 | -export_type([map_sum_t/0]). 8 | 9 | -type map_sum_t() :: #{field_one := _} 10 | | #{field_two := _}. 11 | 12 | -spec map_variants(map_sum_t()) -> ok. 13 | map_variants(T) -> 14 | case T of 15 | #{field_one := _} -> ok 16 | end. 17 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/exhaustive_remote_map_variants.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_remote_map_variants). 2 | 3 | %% This is the remote type counterpart 4 | %% of test/known_problems/should_fail/exhaustive_map_variants.erl 5 | %% 6 | %% See also test/should_fail/exhaustive_user_type.erl 7 | %% and test/should_fail/exhaustive_remote_user_type.erl 8 | 9 | -export([remote_map_variants/1]). 10 | 11 | -spec remote_map_variants(exhaustive_user_type:map_sum_t()) -> ok. 12 | remote_map_variants(T) -> 13 | case T of 14 | #{field_one := _} -> ok 15 | end. 16 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/guard_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(guard_should_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec wrong_arity(fun((any()) -> any()) | other) -> fun((any()) -> any()) | not_function. 6 | wrong_arity(F) when is_function(F, 2) -> F; 7 | wrong_arity(_) -> not_function. 8 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/infer_any_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(infer_any_pattern). 2 | 3 | -export([pat_any/1]). 4 | 5 | %% We would expect (at least when infer mode is enabled) that type of 6 | %% I, S and L is integer(), string() and list() respectively but 7 | %% currently they all become any() 8 | %% (which is fine, but Gradualizer could be more precise) 9 | -spec pat_any(any()) -> float(). 10 | pat_any({<>}) -> I; 11 | pat_any({S = "string"}) -> S; 12 | pat_any({"prefix" ++ L}) -> L. 13 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/intersection_with_any_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_with_any_should_fail). 2 | 3 | -export([any_refined_using_guard/1, 4 | var_as_pattern/1, 5 | var_inside_pattern/1]). 6 | 7 | %% X :: any() & atom() by refinement 8 | -spec any_refined_using_guard(any()) -> 5. 9 | any_refined_using_guard(X) when is_atom(X) -> 10 | X. 11 | 12 | -spec var_as_pattern(atom()) -> integer(). 13 | var_as_pattern(Atom) -> 14 | case get_any() of 15 | Atom -> 16 | %% at this point Atom :: any() 17 | %% but we want Atom :: atom() & any() 18 | Atom 19 | end. 20 | 21 | -spec var_inside_pattern(atom()) -> integer(). 22 | var_inside_pattern(Atom) -> 23 | case get_any() of 24 | {Atom} -> 25 | %% at this point Atom :: any() 26 | %% but we want Atom :: atom() & any() 27 | Atom 28 | end. 29 | 30 | %% helper 31 | -spec get_any() -> any(). 32 | get_any() -> 33 | receive Any -> Any end. 34 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/intersection_with_unreachable.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_with_unreachable). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% Expected error: The clause on line 10 at column 1 cannot be reached. 6 | -spec f(a, b) -> c; 7 | (e, f) -> g. 8 | f(a, b) -> c; 9 | f(e, f) -> g; 10 | f(x, x) -> y. -------------------------------------------------------------------------------- /test/known_problems/should_fail/lambda_wrong_args.erl: -------------------------------------------------------------------------------- 1 | -module(lambda_wrong_args). 2 | 3 | -export([f/1]). 4 | 5 | %% The argument type of F should be inferred to be the same 6 | %% as the argument type of integer_to_binary/1, ie integer(). 7 | -spec f(list()) -> binary(). 8 | f(Int) -> 9 | F = fun(I) -> integer_to_binary(I) end, 10 | F(Int). 11 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/map_refinement_fancy.erl: -------------------------------------------------------------------------------- 1 | -module(map_refinement_fancy). 2 | 3 | -export([map_refinement_fancy/1, just_a_bit_less_fancy/1]). 4 | 5 | %% Expected exhaustiveness error: Values of type #{1 := 3, 2 := 4} not covered. 6 | %% 7 | %% Explanation: exhaustiveness checking is disabled for maps with non-singleton 8 | %% required (exact assoc) keys. 9 | -spec map_refinement_fancy(#{1..2 := 3..4}) -> ok. 10 | map_refinement_fancy(#{1 := 4}) -> 11 | ok; 12 | map_refinement_fancy(#{2 := 3}) -> 13 | ok. 14 | 15 | %% Expected exhaustiveness error: Values #{x => a} and #{y => a} not covered. 16 | %% 17 | %% Explanation: exhaustiveness checking is disabled for maps with non-singleton 18 | %% required (exact assoc) keys. 19 | -spec just_a_bit_less_fancy(#{x|y := a}) -> ok. 20 | just_a_bit_less_fancy(#{x := a, y := a}) -> ok. -------------------------------------------------------------------------------- /test/known_problems/should_fail/maybe_expr_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(maybe_expr_should_fail). 2 | 3 | -ifdef(OTP_RELEASE). 4 | -if(?OTP_RELEASE >= 25). 5 | -if(?FEATURE_AVAILABLE(maybe_expr)). 6 | 7 | -feature(maybe_expr, enable). 8 | 9 | -export([check1/0, check2/0]). 10 | -export([infer1/0, infer2/1]). 11 | 12 | -spec check1() -> integer(). 13 | check1() -> 14 | maybe 15 | ok ?= ok, 16 | "one" 17 | end. 18 | 19 | -spec check2() -> integer(). 20 | check2() -> 21 | maybe 22 | ok ?= not_ok, 23 | one 24 | else 25 | _ -> "two" 26 | end. 27 | 28 | -spec infer1() -> integer(). 29 | infer1() -> 30 | R = maybe 31 | ok ?= ok 32 | end, 33 | R. 34 | 35 | -spec infer2(string()) -> integer(). 36 | infer2(Val) -> 37 | R = maybe 38 | ok ?= Val 39 | else 40 | _ -> ok 41 | end, 42 | R. 43 | 44 | -endif. %% FEATURE_AVAILABLE 45 | -endif. %% OTP >= 25 46 | -endif. %% OTP_RELEASE 47 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/poly_lists_map_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(poly_lists_map_should_fail). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([i/1, 6 | j/1]). 7 | 8 | -spec i([binary() | integer()]) -> [integer()]. 9 | i(L) -> 10 | lists:map(fun 11 | (I) when is_integer(I) -> I * 2; 12 | (B) when is_list(B) -> list_to_integer(B) 13 | end, L). 14 | 15 | -spec j([binary() | integer()]) -> [integer()]. 16 | j(L) -> 17 | lists:map(fun 18 | (I) when is_integer(I) -> I * 2 19 | end, L). 20 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/recursive_types_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(recursive_types_should_fail). 2 | 3 | -export([recursive_param1/1, 4 | recursive_param3/1]). 5 | 6 | -type rec1(A) :: A | rec1({A | rec1(A)}). 7 | 8 | -spec recursive_param1(rec1(integer())) -> ok. 9 | recursive_param1({qwe, zxc}) -> ok. 10 | 11 | -type rec3(A) :: A | rec3(A). 12 | 13 | %% `ok' is not an `integer()' - this should fail. 14 | -spec recursive_param3(rec3(integer())) -> atom(). 15 | recursive_param3(ok) -> ok. -------------------------------------------------------------------------------- /test/known_problems/should_fail/refine_ty_vars.erl: -------------------------------------------------------------------------------- 1 | -module(refine_ty_vars). 2 | 3 | -export([foo/1]). 4 | 5 | %% Since _X gets the type any(), no refinement occurs. 6 | -spec foo([{a|b, a|b}]) -> boolean(). 7 | foo(Xs) -> 8 | lists_any(fun ({a, _X}) -> false; 9 | ({a, a}) -> true; %% can never match 10 | ({b, _}) -> true 11 | end, 12 | Xs). 13 | 14 | %% An isolated version of lists:any/2 15 | -spec lists_any(fun((T) -> boolean()), [T]) -> boolean(). 16 | lists_any(Pred, [X|Xs]) -> Pred(X) orelse lists_any(Pred, Xs); 17 | lists_any(_Pred, []) -> false. 18 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/sample.erl: -------------------------------------------------------------------------------- 1 | % From #318 2 | -module(sample). 3 | -export([test/1]). 4 | 5 | -spec test(atom()) -> pid(). 6 | test(X) -> fapply(fun idz/1, X). 7 | 8 | -spec idz(Z) -> Z. 9 | idz(Z) -> Z. 10 | 11 | -spec fapply(fun((T) -> U), T) -> U. 12 | fapply(F, X) -> 13 | ZZ = F(X), 14 | ZZ. 15 | -------------------------------------------------------------------------------- /test/known_problems/should_fail/type_refinement_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(type_refinement_should_fail). 2 | 3 | -export([guard_prevents_refinement/2, 4 | guard_prevents_refinement2/1, 5 | pattern_prevents_refinement/2]). 6 | 7 | -spec guard_prevents_refinement(1..2, boolean()) -> 2. 8 | guard_prevents_refinement(N, Guard) -> 9 | case N of 10 | 1 when Guard -> 2; 11 | M -> M %% 1 cannot be eliminated 12 | end. 13 | 14 | -spec guard_prevents_refinement2(integer()) -> ok. 15 | guard_prevents_refinement2(X) when is_integer(X), X rem 7 == 0 -> ok; 16 | guard_prevents_refinement2(X) -> ok. % X can still be an integer 17 | 18 | -spec pattern_prevents_refinement(integer(), any()) -> atom(). 19 | pattern_prevents_refinement(X, X) when is_integer(X) -> ok; 20 | pattern_prevents_refinement(X, {_Y}) when is_integer(X) -> ok; 21 | pattern_prevents_refinement(Inf, _) -> Inf. % Inf can still be an integer 22 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/README.md: -------------------------------------------------------------------------------- 1 | Add modules here that are meant to pass typechecking but at the moment they 2 | either crash Gradualizer or happen to not pass typechecking. 3 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/arith_op_arg_types.erl: -------------------------------------------------------------------------------- 1 | -module(arith_op_arg_types). 2 | 3 | -export([arg1/0, arg2/0]). 4 | 5 | %% The issue is that arith_op_arg_types/2 for '-' and non_neg_integer() as return type is too 6 | %% restricitve. 7 | %% "The integer is expected to have type pos_integer() but it has type 0" 8 | -spec arg1() -> non_neg_integer(). 9 | arg1() -> 10 | 0 - 0. 11 | 12 | %% "The integer is expected to have type 1 but it has type 2" 13 | -spec arg2() -> non_neg_integer(). 14 | arg2() -> 15 | 3 - 2. 16 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/binary_exhaustiveness_checking_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(binary_exhaustiveness_checking_should_pass). 2 | 3 | -export([f/0]). 4 | 5 | -spec f() -> ok. 6 | f() -> 7 | Cond = <<"asd">>, 8 | case Cond of 9 | <<_/bytes>> -> ok 10 | end. 11 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/call_intersection_function_with_union_arg_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(call_intersection_function_with_union_arg_should_pass). 2 | 3 | -export([g/2]). 4 | 5 | -type t() :: t1 | t2. 6 | -type u() :: u1 | u2. 7 | 8 | -spec g(t(), u()) -> ok. 9 | g(T, U) -> 10 | g_(T, U). 11 | 12 | -spec g_(t1, u1) -> ok; 13 | (t2, u2) -> ok. 14 | g_(t1, u1) -> ok; 15 | g_(t2, u2) -> ok. 16 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/different_normalization_levels.erl: -------------------------------------------------------------------------------- 1 | -module(different_normalization_levels). 2 | 3 | -export([f/1]). 4 | 5 | -type t() :: a | b | c. 6 | 7 | %% The problem is that the argument type stays the same (as top-level normalization 8 | %% does not expand it) but the result type gets normalized to `a | b | c', 9 | %% and `t() | a' is not a subtype of `a | b | c' because it currently checks whether 10 | %% `t()' is a subtype of one of `a' or `b', or `c' (which it isn't). 11 | 12 | %% It surfaced because, for instance, 13 | %% type() | any() 14 | %% is not a subtype of 15 | %% type() 16 | 17 | -spec f(t() | a) -> t(). 18 | f(X) -> X. 19 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/elixir_list_first.erl: -------------------------------------------------------------------------------- 1 | -module(elixir_list_first). 2 | 3 | -export([f/0]). 4 | 5 | % This is how List.first/1 is defined in Elixir 6 | -spec first([]) -> nil; 7 | ([Elem, ...]) -> Elem. 8 | first([]) -> nil; 9 | first([Element | _]) -> Element. 10 | 11 | -spec f() -> ok. 12 | f() -> 13 | first(some_list()), 14 | ok. 15 | 16 | -spec some_list() -> list(). 17 | some_list() -> [anything]. 18 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/error_in_guard.erl: -------------------------------------------------------------------------------- 1 | -module(error_in_guard). 2 | -compile([debug_info]). 3 | -export([f/1]). 4 | 5 | -record(s, {a :: c | d}). 6 | -record(r, {a :: #s{} | boolean()}). 7 | 8 | -spec f(#r{}) -> boolean(). 9 | f(R) -> if 10 | (R#r.a)#s.a == c -> true; 11 | true -> false 12 | end. 13 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/fun_subtyping.erl: -------------------------------------------------------------------------------- 1 | -module(fun_subtyping). 2 | 3 | -export([return_fun_intersection/0]). 4 | 5 | -spec return_fun_intersection() -> fun((number()) -> number()). 6 | return_fun_intersection() -> fun number/1. 7 | 8 | -spec number(integer()) -> integer(); 9 | (float()) -> float(). 10 | number(N) -> N. 11 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/generator_var_shadow.erl: -------------------------------------------------------------------------------- 1 | -module(generator_var_shadow). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | -compile([nowarn_shadow_vars, nowarn_unused_vars]). 5 | 6 | -spec f(boolean(), [integer()]) -> [integer()]. 7 | f(X, Xs) -> 8 | [X || X <- Xs]. 9 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/inner_union_subtype_of_root_union.erl: -------------------------------------------------------------------------------- 1 | -module(inner_union_subtype_of_root_union). 2 | 3 | -export([tuple_case/0, map_case/1]). 4 | 5 | % The problem is that {t, a|b} is not subtype of {t, a} | {t, b}. 6 | 7 | % It has surfaced because 8 | % {'type', anno(), 'map_field_assoc' | 'map_field_exact', [abstract_type()]} 9 | % is not a subtype of 10 | % {'type', anno(), 'map_field_assoc', [abstract_type()]} 11 | % | {'type', anno(), 'map_field_exact', [abstract_type()]} 12 | 13 | -spec g() -> a | b. 14 | g() -> a. 15 | 16 | -spec tuple_case() -> {t, a} | {t, b}. 17 | tuple_case() -> 18 | {t, g()}. 19 | 20 | % The same thing holds for maps. 21 | -spec map_case(#{a => b | c}) -> #{a => b} | #{a => c}. 22 | map_case(M) -> M. -------------------------------------------------------------------------------- /test/known_problems/should_pass/intersection_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_should_pass). 2 | 3 | -export([j/1]). 4 | 5 | -spec i(a, b) -> {a, b}; 6 | (d, e) -> {d, e}. 7 | i(V, U) -> {V, U}. 8 | 9 | -spec j({a, b} | {d, e}) -> {a, b} | {d, e}. 10 | j({V, U}) -> i(V, U). 11 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/intersection_with_any.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_with_any). 2 | 3 | -export([intersection_using_constraints/1]). 4 | 5 | %% X :: integer() & any() by the constraints 6 | -spec intersection_using_constraints(X) -> 5 when X :: integer(), X :: any(). 7 | intersection_using_constraints(X) -> 8 | % X :: integer() & any() at this point, 9 | % which should be compatible with X :: 5 10 | X. 11 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/list_concat_op_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(list_concat_op_should_pass). 2 | 3 | -export([nil_concat_op_elem_gives_elem/0, 4 | nonempty1_concat_op_elem_gives_improper/0, 5 | nonempty2_concat_op_elem_gives_improper/0]). 6 | 7 | -spec nil_concat_op_elem_gives_elem() -> b. 8 | nil_concat_op_elem_gives_elem() -> 9 | [] ++ b. 10 | 11 | -spec nonempty1_concat_op_elem_gives_improper() -> nonempty_improper_list(a, b). 12 | nonempty1_concat_op_elem_gives_improper() -> 13 | [a] ++ b. 14 | 15 | -spec nonempty2_concat_op_elem_gives_improper() -> nonempty_improper_list(atom(), c). 16 | nonempty2_concat_op_elem_gives_improper() -> 17 | [a,b] ++ c. 18 | 19 | %% See list_op_should_fail. 20 | %% -spec improper_concat_op_elem_gives_badarg() -> none(). 21 | %% improper_concat_op_elem_gives_badarg() -> 22 | %% [a|b] ++ c. 23 | 24 | %% See list_op_should_fail. 25 | %% -spec improper_concat_op_nonempty_gives_badarg() -> none(). 26 | %% improper_concat_op_nonempty_gives_badarg() -> 27 | %% [a|b] ++ [c]. 28 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/list_tail.erl: -------------------------------------------------------------------------------- 1 | -module(list_tail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% Improper list in type inference - currently considered a type error 6 | atom_tail() -> 7 | [ 1 | list_to_atom("banana")]. 8 | 9 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/map_pattern_duplicate_key.erl: -------------------------------------------------------------------------------- 1 | -module(map_pattern_duplicate_key). 2 | 3 | -export([f/1]). 4 | 5 | % Fails with: The expression x on line 6 at column 13 is not a valid key in the map type #{x := a} 6 | -spec f(#{x := a}) -> true. 7 | f(#{x := a, x := a}) -> true. -------------------------------------------------------------------------------- /test/known_problems/should_pass/poly_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(poly_should_pass). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([l/0]). 6 | 7 | -type t1() :: {}. 8 | -type t2() :: binary(). 9 | -type list_of_unions() :: [t1() | t2()]. 10 | 11 | %% This fails with: 12 | %% 13 | %% Lower bound [t1() | t2()] of type variable B_typechecker_3529_12 on line 25 14 | %% is not a subtype of t1() | t2() 15 | %% 16 | %% Now, why is that the case? 17 | %% `return_list_of_unions/1' returns just that - a list of `t1() | t2()' unions. 18 | %% This means that `has_intersection_spec/1' passed in to `lists:map/2' would be called with 19 | %% a union as the arg, not a list. 20 | %% This should mean that it would also return a `t1() | t2()' union, so the final return value from 21 | %% `l/0' should be `[t1() | t2()]'. 22 | %% However, the constraint solver is not able to tell that only one clause of the 23 | %% multi-clause spec would suffice and it uses both clauses' return types as lower bound on `B'. 24 | %% 25 | %% In practice, this means that we should avoid functions 26 | %% with intersection types like `(a()) -> b() & ([a()]) -> [b()]', 27 | %% because the constraint solver can't cope with them. 28 | %% We should instead define two separate functions: `(a()) -> b()' and `([a()]) -> [b()]', 29 | %% and check outside of them which to call based on the type of the parameter. 30 | %% 31 | %% See also `l/0' in `test/should_pass/poly_pass.erl'. 32 | -spec l() -> [t1() | t2()]. 33 | l() -> 34 | lists:map(fun has_intersection_spec/1, return_list_of_unions([])). 35 | 36 | -spec has_intersection_spec(t1() | t2()) -> t1() | t2(); 37 | (list()) -> [t1() | t2()]. 38 | has_intersection_spec([]) -> []; 39 | has_intersection_spec([_|_] = L) -> lists:map(fun has_intersection_spec/1, L); 40 | has_intersection_spec(T) -> T. 41 | 42 | -spec return_list_of_unions(list_of_unions()) -> list_of_unions(). 43 | return_list_of_unions(_L) -> []. 44 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/poly_type_vars.erl: -------------------------------------------------------------------------------- 1 | -module(poly_type_vars). 2 | 3 | -export([foo/1]). 4 | 5 | -gradualizer([solve_constraints]). 6 | 7 | %% When a spec contains bounded type variables (bound quantification), 8 | %% we currently only substitute these type variables with their bounds, 9 | %% effectively making these functions monomorphic. 10 | 11 | -spec foo([{integer(), integer()}]) -> [{integer(), integer()}]. 12 | foo(Pairs) -> 13 | pair_sort(Pairs). 14 | 15 | -spec pair_sort([A]) -> [A] when A :: {gradualizer:top(), gradualizer:top()}. 16 | pair_sort(Ps) -> Ps. 17 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/recursive_types.erl: -------------------------------------------------------------------------------- 1 | -module(recursive_types). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -type rec(A) :: A | {rec, rec(A)}. 6 | 7 | -spec unwrap(rec(rec(atom()))) -> rec(atom()). 8 | unwrap({rec, Elem}) -> Elem. 9 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/refine_bound_var_on_mismatch.erl: -------------------------------------------------------------------------------- 1 | -module(refine_bound_var_on_mismatch). 2 | 3 | %% Note: Here we're refining an already bound variable 4 | 5 | -export([refined_var_not_matching_itself/1, 6 | refine_bound_var_by_pattern_mismatch/1]). 7 | 8 | %% Current error: Var is expected to have type y | z but has type x | y | z 9 | -spec refined_var_not_matching_itself(x | y | z) -> ok. 10 | refined_var_not_matching_itself(Var) -> 11 | case Var of 12 | x -> ok; 13 | Var -> ok 14 | end. 15 | 16 | %% Current error: Var is expected to have type ok but it has type ok | nok 17 | -spec refine_bound_var_by_pattern_mismatch(ok | nok) -> ok. 18 | refine_bound_var_by_pattern_mismatch(Var) -> 19 | case Var of 20 | nok -> ok; 21 | _ -> Var 22 | end. 23 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/refine_bound_var_with_guard_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(refine_bound_var_with_guard_should_pass). 2 | 3 | -export([f/1, 4 | too_complex_guards/1]). 5 | 6 | -gradualizer([no_skip_complex_guards]). 7 | 8 | %% This type is simpler than gradualizer_type:abstract_type() by having less variants 9 | %% and by using tuples to contain deeper nodes. The latter frees us from having to deal 10 | %% with list (non)emptiness/length in the patterns. 11 | -type simple_type() :: {type, list | nonempty_list, {a}} 12 | | {type, atom, {b}}. 13 | 14 | -spec f(simple_type()) -> a. 15 | f({type, T, {InnerNode}}) 16 | when T == list orelse T == nonempty_list -> 17 | %% Currently, this clause fails with: 18 | %% 19 | %% test/known_problems/should_pass/refine_bound_var_with_guard_should_pass.erl: 20 | %% The variable on line 14 at column 5 is expected to have type a but it has type b | a 21 | %% 22 | %% f({type, T, {InnerNode}}) 23 | %% when T == list orelse T == nonempty_list -> 24 | %% InnerNode; 25 | %% ^^^^^^ 26 | %% 27 | %% We can refactor it to pass (see g/1), but then we end up with almost identical repeated code. 28 | %% The end result is more clunky and doesn't feel as lightweight and flexible as normal Erlang. 29 | InnerNode; 30 | f({type, atom, {_InnerNode}}) -> 31 | a. 32 | 33 | %% Intentionally not exported. 34 | -spec g(simple_type()) -> a. 35 | g({type, list, {InnerNode}}) -> 36 | InnerNode; 37 | g({type, nonempty_list, {InnerNode}}) -> 38 | InnerNode; 39 | g({type, atom, {_InnerNode}}) -> 40 | a. 41 | 42 | %% See too_complex_guards thrown in src/typechecker.erl. 43 | -spec too_complex_guards(integer() | [integer()] | none) -> number(). 44 | too_complex_guards([_|_] = Ints) -> 45 | lists:sum(Ints); 46 | too_complex_guards(EmptyOrNone) when EmptyOrNone =:= none orelse is_list(EmptyOrNone) -> 47 | 0; 48 | too_complex_guards(Int) -> 49 | Int. 50 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/refine_comparison_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(refine_comparison_should_pass). 2 | 3 | -export([comp_map_value3/1]). 4 | 5 | -type my_map() :: #{value := integer() | nil}. 6 | 7 | -spec comp_map_value3(my_map()) -> integer(). 8 | comp_map_value3(State) when map_get(value, State) /= nil -> 9 | case State of 10 | #{value := Val} -> 11 | Val + 1 12 | end; 13 | comp_map_value3(#{value := nil}) -> 0. 14 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/refine_list_tail.erl: -------------------------------------------------------------------------------- 1 | -module(refine_list_tail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% Currently fails with: 6 | %% "The variable 'T' has type [] | [any(), ...] but is expected to have type [any(), ...]" 7 | -spec droplast(nonempty_list(T)) -> list(T). 8 | droplast([_T]) -> []; 9 | droplast([H|T]) -> [H|droplast(T)]. 10 | -------------------------------------------------------------------------------- /test/known_problems/should_pass/union_fun.erl: -------------------------------------------------------------------------------- 1 | -module(union_fun). 2 | 3 | -export([ 4 | union_fun_arg/2, 5 | union_fun_arg_var/2 6 | ]). 7 | 8 | %% Prints the following error: 9 | %% The variable on line 14 at column 5 is expected to have type fun((...) -> any()) 10 | %% but it has type fun((integer()) -> integer()) | fun((number()) -> atom()) 11 | 12 | -spec union_fun_arg(fun ((integer()) -> integer()) | fun ((number()) -> atom()), integer()) -> integer() | atom(). 13 | union_fun_arg(F, Int) -> 14 | F(Int). 15 | 16 | -spec union_fun_arg_var(fun ((integer()) -> integer()) | fun ((number()) -> atom()), integer()) -> integer() | atom(). 17 | union_fun_arg_var(F, Int) -> 18 | X = F(Int), 19 | X. 20 | -------------------------------------------------------------------------------- /test/misc/append.erl: -------------------------------------------------------------------------------- 1 | -module(append). 2 | 3 | -compile(export_all). 4 | 5 | % Thanks to Kostis Sagonas for suggesting this example 6 | 7 | app1([], Ys) -> Ys; 8 | app1([X|Xs], Ys) -> [X|app1(Xs, Ys)]. 9 | 10 | -spec app2(list(), list()) -> list(). 11 | app2([], Ys) -> Ys; 12 | app2([X|Xs], Ys) -> [X|app2(Xs, Ys)]. 13 | 14 | -spec app3(list(), integer() | list()) -> integer() | list(). 15 | app3([], Ys) -> Ys; 16 | app3([X|Xs], Ys) -> [X|app3(Xs, Ys)]. 17 | 18 | -spec app4(list(A), list(A)) -> list(A). 19 | app4([], Ys) -> Ys; 20 | app4([X|Xs], Ys) -> [X|app4(Xs, Ys)]. 21 | 22 | test() -> 23 | app3([1,2,3], 4). 24 | -------------------------------------------------------------------------------- /test/misc/int.erl: -------------------------------------------------------------------------------- 1 | -module(int). 2 | 3 | -compile([export_all]). 4 | 5 | -spec f(integer()) -> integer(). 6 | f(N) -> 7 | N. 8 | 9 | -spec g(integer()) -> integer(). 10 | g(1) -> 11 | 2. 12 | -------------------------------------------------------------------------------- /test/misc/lambda.erl: -------------------------------------------------------------------------------- 1 | -module(lambda). 2 | 3 | -compile([export_all]). 4 | 5 | f() -> (fun (X) -> X + 1 end)(2). 6 | -------------------------------------------------------------------------------- /test/misc/lint_errors.erl: -------------------------------------------------------------------------------- 1 | %% This module doesn't even compile. The errors here are caught by erl_lint. 2 | -module(lint_errors). 3 | -export([local_type/0, 4 | local_call/0, 5 | one_more_for_the_record/0, 6 | local_record/1, 7 | record_field/1, 8 | illegal_binary_segment/1, 9 | invalid_record_info/0, 10 | illegal_pattern/1]). 11 | 12 | -spec local_type() -> undefined_type(). 13 | local_type() -> ok. 14 | 15 | -spec local_call() -> ok. 16 | local_call() -> undefined_call(). 17 | 18 | -record(r, {a :: #s{}}). 19 | 20 | %% The number of expected errors are the number of exported functions, 21 | %% so we create a function without errors, to account for the error in 22 | %% the record definition above. 23 | one_more_for_the_record() -> ok. 24 | 25 | -spec local_record(#r{}) -> boolean(). 26 | local_record(R) -> if 27 | (R#r.a)#s.a == c -> true; 28 | true -> false 29 | end. 30 | 31 | -spec record_field(#r{}) -> boolean(). 32 | record_field(R) -> if 33 | R#r.b == c -> true; 34 | true -> false 35 | end. 36 | 37 | illegal_binary_segment(X) -> 38 | <>. %% Size not allowed with utf8/16/32 39 | 40 | invalid_record_info() -> record_info(foo, bar). 41 | 42 | -spec illegal_pattern(gradualizer:top()) -> gradualizer:top(). 43 | illegal_pattern(1 + A) -> ok. 44 | -------------------------------------------------------------------------------- /test/misc/mfa.erl: -------------------------------------------------------------------------------- 1 | -module(mfa). 2 | 3 | -compile([export_all]). 4 | 5 | mfa() -> 6 | epp:parse_forms("mfa.erl",[]). 7 | -------------------------------------------------------------------------------- /test/misc/singleton.erl: -------------------------------------------------------------------------------- 1 | -module(singleton). 2 | 3 | -export([sing/1, atom/1]). 4 | 5 | -spec sing(0|1) -> 10..12. 6 | sing(0) -> 7 | 10; 8 | sing(1) -> 9 | 12. 10 | 11 | -spec atom(a) -> b. 12 | atom(a) -> 13 | b. 14 | -------------------------------------------------------------------------------- /test/misc/undefined_errors.erl: -------------------------------------------------------------------------------- 1 | -module(undefined_errors). 2 | -export([remote_type/0, 3 | remote_remote_type/0, 4 | remote_struct_with_remote_type/0, 5 | remote_struct_with_local_type/0, 6 | remote_struct_with_record/0, 7 | remote_call/0, 8 | remote_record/0, 9 | normalize_remote_type/0, 10 | not_exported/0]). 11 | 12 | -spec remote_type() -> undefined_errors_helper:j(). 13 | remote_type() -> ok. 14 | 15 | -spec remote_remote_type() -> undefined_errors_helper:expands_to_undefined_remote(). 16 | remote_remote_type() -> ok. 17 | 18 | -spec remote_struct_with_remote_type() -> undefined_errors_helper:expands_to_struct_with_undefined_remote(). 19 | remote_struct_with_remote_type() -> {struct, ok}. 20 | 21 | -spec remote_struct_with_local_type() -> undefined_errors_helper:expands_to_struct_with_undefined_local(). 22 | remote_struct_with_local_type() -> {struct, ok}. 23 | 24 | -spec remote_struct_with_record() -> undefined_errors_helper:expands_to_struct_with_undefined_record(). 25 | remote_struct_with_record() -> {struct, ok}. 26 | 27 | -spec remote_call() -> ok. 28 | remote_call() -> undefined_errors_helper:undefined_call(). 29 | 30 | -record(defined_record, {a, b, c}). 31 | -spec remote_record() -> #defined_record{}. 32 | remote_record() -> undefined_errors_helper:und_rec(). 33 | 34 | -spec normalize_remote_type() -> ok. 35 | normalize_remote_type() -> undefined_errors_helper:und_ty(). 36 | 37 | -spec not_exported() -> undefined_errors_helper:not_exported_type(). 38 | not_exported() -> undefined_errors_helper:not_exp_ty(). 39 | -------------------------------------------------------------------------------- /test/property_test/gradualizer_gen.erl: -------------------------------------------------------------------------------- 1 | -module(gradualizer_gen). 2 | 3 | -export([abstract_type/0, abstract_type/1, 4 | expr/0, expr/1, 5 | module/0, module/1]). 6 | 7 | abstract_type() -> 8 | Opts = [{weight, {binop, 0}}, 9 | {weight, {unop, 0}}, 10 | {weight, {remote_type, 0}}, 11 | {weight, {annotated_type, 0}}, 12 | {weight, {user_defined_type, 10}}], 13 | abstract_type(Opts). 14 | 15 | abstract_type(Opts) -> 16 | State = gradualizer_erlang_abstract_code:options(Opts), 17 | gradualizer_erlang_abstract_code:abstract_type(State). 18 | 19 | expr() -> 20 | Exclude = [%% TODO: Due to some strange reason, maps or other full blown types get generated 21 | %% as bitstring segments 22 | bitstring, 23 | %% Possibly remote function calls 24 | termcall, varcall, localcall, extcall, 25 | %% Remote fun refs 26 | ext_mfa, any_mfa], 27 | ExcludeWeights = [ {weight, {Tag, 0}} || Tag <- Exclude ], 28 | Opts = ExcludeWeights, 29 | expr(Opts). 30 | 31 | expr(Opts) -> 32 | gradualizer_erlang_abstract_code:expr(Opts). 33 | 34 | module() -> 35 | %% See expr() generator. 36 | Exclude = [bitstring, 37 | termcall, varcall, localcall, extcall, 38 | ext_mfa, any_mfa, 39 | record_field_access], 40 | %% TODO: define records before enabling their generation again 41 | ExcludeWeights = [ {weight, {Tag, 0}} || Tag <- Exclude ], 42 | %% The generator might generate function clauses with different number of params. 43 | %% This makes typechecking fail and is not expected in real world. 44 | %% TODO: Limit to just one-clause functions. 45 | Limits = [{limit, {function_clauses, 1}}], 46 | Opts = ExcludeWeights ++ Limits, 47 | module(Opts). 48 | 49 | module(Opts) -> 50 | gradualizer_erlang_abstract_code:module(Opts). 51 | -------------------------------------------------------------------------------- /test/should_fail/annotated_types_fail.erl: -------------------------------------------------------------------------------- 1 | -module(annotated_types_fail). 2 | 3 | -export([downcast_using_annotation/1, 4 | type_error_after_annotation/0, type_error_after_annotation/1, 5 | incompatible_cast/0, incompatible_cast/1, 6 | syntax_error_1/0, syntax_error_2/0]). 7 | 8 | -include("../../include/gradualizer.hrl"). 9 | 10 | -spec downcast_using_annotation(integer()) -> any(). 11 | downcast_using_annotation(N) -> 12 | ?annotate_type(N, pos_integer()). 13 | 14 | -spec type_error_after_annotation() -> non_neg_integer(). 15 | type_error_after_annotation() -> 16 | receive {int, N} -> ?annotate_type(N, integer()) end. 17 | 18 | type_error_after_annotation(X) -> 19 | N = ?annotate_type(X, integer()), 20 | atom_or_die(N). 21 | 22 | -spec incompatible_cast() -> atom(). 23 | incompatible_cast() -> 24 | Atom = ?assert_type({"yyy", list_to_atom("zzzz")}, atom()), 25 | Atom. 26 | 27 | -spec incompatible_cast(integer()) -> atom(). 28 | incompatible_cast(N) -> 29 | ?assert_type(N, atom()). 30 | 31 | syntax_error_1() -> 32 | '::'(banana, "not a type"). 33 | 34 | -spec syntax_error_2() -> atom(). 35 | syntax_error_2() -> 36 | ':::'(banana, "not a type"). 37 | 38 | %% Used by the tests functions 39 | -spec atom_or_die(atom()) -> ok. 40 | atom_or_die(A) when is_atom(A) -> ok. 41 | -------------------------------------------------------------------------------- /test/should_fail/arg.erl: -------------------------------------------------------------------------------- 1 | -module(arg). 2 | 3 | -export([g/1]). 4 | 5 | -spec f(integer()) -> integer(). 6 | f(N) -> 7 | N. 8 | 9 | -spec g(boolean()) -> boolean(). 10 | g(N) -> 11 | f(N). 12 | -------------------------------------------------------------------------------- /test/should_fail/arith_op_fail.erl: -------------------------------------------------------------------------------- 1 | -module(arith_op_fail). 2 | 3 | -export([failplus/1, faildivvar/1, faildivlit/1, failpositivedivision/1, 4 | faildivprecise/1, failplusprecise/2, failminusprecisepos/2, 5 | failminusnonneg/2, failminuspreciseneg/2, 6 | failbnot/1, int_error/2, int_error2/2]). 7 | 8 | -spec failplus(_) -> tuple(). 9 | failplus(X) -> X + X. 10 | 11 | -spec faildivvar(_) -> boolean(). 12 | faildivvar(X) -> X div X. 13 | 14 | -spec faildivlit(boolean()) -> any() | boolean(). 15 | faildivlit(X) -> X div 2. 16 | 17 | -spec failpositivedivision(integer()) -> neg_integer(). 18 | failpositivedivision(X) -> X / X. 19 | 20 | -spec faildivprecise(1..10) -> 1..10 | atom. 21 | faildivprecise(X) -> X div X. 22 | 23 | -spec failplusprecise(5, 2 | 4) -> 7 | 9. 24 | failplusprecise(X, Y) -> X + Y. 25 | 26 | -spec failminusprecisepos(pos_integer(), neg_integer()) -> pos_integer(). 27 | failminusprecisepos(X, Y) -> X - Y. 28 | 29 | -spec failminusnonneg(non_neg_integer(), neg_integer()) -> non_neg_integer(). 30 | failminusnonneg(X, Y) -> X - Y. 31 | 32 | -spec failminuspreciseneg(neg_integer(), non_neg_integer()) -> neg_integer(). 33 | failminuspreciseneg(X, Y) -> X - Y. 34 | 35 | -spec failbnot(string()) -> integer(). 36 | failbnot(S) -> 37 | O = bnot(S), 38 | O. 39 | 40 | -spec int_error(any(), atom()) -> integer(). 41 | int_error(X, Y) -> 42 | A = X div Y, 43 | A. 44 | 45 | -spec int_error2(float(), float()) -> integer(). 46 | int_error2(X, Y) -> 47 | A = X div Y, 48 | A. 49 | -------------------------------------------------------------------------------- /test/should_fail/arity_mismatch.erl: -------------------------------------------------------------------------------- 1 | -module(arity_mismatch). 2 | 3 | -export([bar/0, bar/1]). 4 | 5 | -spec foo(alice) -> bob. 6 | foo(alice) -> bob. 7 | 8 | -spec bar(fun((alice, bob) -> bob)) -> bob. 9 | bar(F) -> F(alice). 10 | 11 | -spec bar() -> bob. 12 | bar() -> (fun foo/1)(). 13 | -------------------------------------------------------------------------------- /test/should_fail/bc_fail.erl: -------------------------------------------------------------------------------- 1 | -module(bc_fail). 2 | -export([f/0, non_bin_expr/0, integer_signed_wrong/1]). 3 | 4 | -spec f() -> binary(). 5 | f() -> 6 | << X || <> <= <<"abc">> >>. 7 | 8 | non_bin_expr() -> 9 | << (list_to_integer(X)) || X <- ["42"] >>. 10 | 11 | -spec integer_signed_wrong(binary()) -> non_neg_integer(). 12 | integer_signed_wrong(B) -> 13 | <> = B, 14 | A. 15 | 16 | -------------------------------------------------------------------------------- /test/should_fail/bin_expression.erl: -------------------------------------------------------------------------------- 1 | -module(bin_expression). 2 | -export([bin_1/0, bin_2/2]). 3 | 4 | -spec bin_1() -> binary(). 5 | bin_1() -> 6 | <<1:1>>. 7 | 8 | -spec bin_2(any(), any()) -> <<_:_*6>>. 9 | bin_2(A, B) -> 10 | <<0:A/integer-unit:27, 1:B/integer-unit:30>>. -------------------------------------------------------------------------------- /test/should_fail/bin_type_error.erl: -------------------------------------------------------------------------------- 1 | -module(bin_type_error). 2 | 3 | -export([throws_bin_type_error/1]). 4 | 5 | -spec throws_bin_type_error(integer()) -> any(). 6 | throws_bin_type_error(<<>>) -> ok. 7 | -------------------------------------------------------------------------------- /test/should_fail/branch.erl: -------------------------------------------------------------------------------- 1 | -module(branch). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec c(boolean()) -> integer(). 6 | c(X) -> 7 | X. 8 | -------------------------------------------------------------------------------- /test/should_fail/branch2.erl: -------------------------------------------------------------------------------- 1 | -module(branch2). 2 | 3 | -export([c/1]). 4 | 5 | -spec c(boolean()) -> integer(). 6 | c(true) -> 7 | 1; 8 | c(false) -> 9 | apa. 10 | -------------------------------------------------------------------------------- /test/should_fail/call.erl: -------------------------------------------------------------------------------- 1 | -module(call). 2 | 3 | -export([g/0, h/1, apply/1, remote/0]). 4 | 5 | -spec f() -> {}. 6 | f() -> 7 | {}. 8 | 9 | -spec g() -> boolean(). 10 | g() -> 11 | f(). 12 | 13 | -spec h(boolean()) -> integer(). 14 | h(B) -> 15 | B(). 16 | 17 | -spec apply(fun((...) -> boolean())) -> integer(). 18 | apply(F) -> 19 | F(). 20 | 21 | -spec remote() -> integer(). 22 | remote() -> 23 | io_lib:format("", []). 24 | -------------------------------------------------------------------------------- /test/should_fail/call_intersection_function_with_union_arg_fail.erl: -------------------------------------------------------------------------------- 1 | -module(call_intersection_function_with_union_arg_fail). 2 | 3 | -export([i1/2, 4 | i2/2]). 5 | 6 | -type t() :: t1 | t2. 7 | -type u() :: u1 | u2. 8 | 9 | -spec i1(t1, u2) -> two. 10 | i1(T, U) -> 11 | j(T, U). 12 | 13 | -spec i2(t(), u()) -> one | two. 14 | i2(T, U) -> 15 | j(T, U). 16 | 17 | -spec j(t1, u1) -> one; 18 | (t2, u2) -> two. 19 | j(t1, u1) -> one; 20 | j(t2, u2) -> two. 21 | -------------------------------------------------------------------------------- /test/should_fail/case_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(case_pattern). 2 | -export([f/2]). 3 | 4 | -spec f(integer(), atom()) -> ok. 5 | f(X, Y) -> 6 | case Y of 7 | X -> ok 8 | end. 9 | -------------------------------------------------------------------------------- /test/should_fail/case_pattern2.erl: -------------------------------------------------------------------------------- 1 | -module(case_pattern2). 2 | -export([f/2]). 3 | 4 | -spec f(integer(), atom()) -> ok. 5 | f(X, Y) -> 6 | case {X, Y} of 7 | {Z, Z} -> ok 8 | end. 9 | -------------------------------------------------------------------------------- /test/should_fail/catch_expr_fail.erl: -------------------------------------------------------------------------------- 1 | -module(catch_expr_fail). 2 | 3 | -export([foo/1]). 4 | 5 | -spec foo(nok) -> ok. 6 | foo(X) -> 7 | A = (catch X), 8 | A. 9 | -------------------------------------------------------------------------------- /test/should_fail/cons.erl: -------------------------------------------------------------------------------- 1 | -module(cons). 2 | 3 | -export([f/0]). 4 | 5 | -spec f() -> []. 6 | f() -> [x, y]. 7 | -------------------------------------------------------------------------------- /test/should_fail/covariant_map_keys_fail.erl: -------------------------------------------------------------------------------- 1 | -module(covariant_map_keys_fail). 2 | 3 | -export([not_good/1]). 4 | 5 | -spec good(#{good := A}) -> A. 6 | good(#{good := X}) -> X. 7 | 8 | -spec not_good(#{good | bad := A}) -> A. 9 | not_good(M) -> good(M). %% This call should fail 10 | -------------------------------------------------------------------------------- /test/should_fail/cyclic_type_vars.erl: -------------------------------------------------------------------------------- 1 | -module(cyclic_type_vars). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec foo(A) -> B when 6 | A :: true | B, 7 | B :: [A]. 8 | foo(_) -> []. 9 | -------------------------------------------------------------------------------- /test/should_fail/depth.erl: -------------------------------------------------------------------------------- 1 | -module(depth). 2 | 3 | -export([bar3/0, bar4/0]). 4 | 5 | -spec bar3() -> {{{0}}}. 6 | bar3() -> {{{1}}}. 7 | 8 | %%% not detected by Dialyzer 9 | -spec bar4() -> {{{{0}}}}. 10 | bar4() -> {{{{1}}}}. 11 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive). 2 | 3 | -export([integer_0/1,integer_1/1 4 | ,pos_integer/1, neg_integer/1 5 | ,tuple_1/1 6 | ,union_atom/1, union_nil/1, union_mix/1 7 | ,annotated/1 8 | ,char/1]). 9 | 10 | -spec integer_0(integer()) -> {}. 11 | integer_0(0) -> 12 | {}. 13 | 14 | -spec integer_1(integer()) -> {}. 15 | integer_1(1) -> 16 | {}. 17 | 18 | -spec pos_integer(pos_integer()) -> {}. 19 | pos_integer(1) -> 20 | {}. 21 | 22 | -spec neg_integer(neg_integer()) -> {}. 23 | neg_integer(-1) -> 24 | {}. 25 | 26 | -spec tuple_1({integer()}) -> {}. 27 | tuple_1({0}) -> 28 | {}. 29 | 30 | -spec union_atom(a | b) -> {}. 31 | union_atom(a) -> 32 | {}. 33 | 34 | -spec union_nil([] | a) -> {}. 35 | union_nil([]) -> 36 | {}. 37 | 38 | -spec union_mix(a | pos_integer()) -> {}. 39 | union_mix(a) -> 40 | {}. 41 | 42 | -spec annotated(X :: integer()) -> {}. 43 | annotated(1) -> 44 | {}. 45 | 46 | -spec char(char()) -> {}. 47 | char($a) -> 48 | {}. 49 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_float.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_float). 2 | 3 | -export([ef/1]). 4 | 5 | -type t() :: {int, integer()} 6 | | {float, float()}. 7 | 8 | -spec ef(t()) -> ok. 9 | ef(T) -> 10 | case T of 11 | {int, _} -> ok 12 | end. 13 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_list_variants.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_list_variants). 2 | 3 | -export([list_variant_omitted/1, 4 | match_on_empty_list/1, 5 | match_on_non_empty_list/1]). 6 | 7 | -type list_variant_t() :: {non_list, integer()} 8 | | {list, [integer()]}. 9 | 10 | -spec list_variant_omitted(list_variant_t()) -> ok. 11 | list_variant_omitted(T) -> 12 | case T of 13 | {non_list, _} -> ok 14 | end. 15 | 16 | -spec match_on_empty_list(list_variant_t()) -> ok. 17 | match_on_empty_list(T) -> 18 | case T of 19 | {non_list, _} -> ok; 20 | {list, []} -> ok 21 | end. 22 | 23 | -spec match_on_non_empty_list(list_variant_t()) -> ok. 24 | match_on_non_empty_list(T) -> 25 | case T of 26 | {non_list, _} -> ok; 27 | {list, [_|_]} -> ok 28 | end. 29 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_refinable_map_variants.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_refinable_map_variants). 2 | 3 | %% This extends cases from test/should_fail/exhaustive_user_type.erl to variants defined as maps. 4 | 5 | -export([map_variants/1]). 6 | 7 | -export_type([map_sum_t/0]). 8 | 9 | -type map_sum_t() :: #{field_one := integer()} 10 | | #{field_two := string()}. 11 | 12 | -spec map_variants(map_sum_t()) -> ok. 13 | map_variants(T) -> 14 | case T of 15 | #{field_one := _} -> ok 16 | end. 17 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_remote_user_type.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_remote_user_type). 2 | 3 | -export([local_alias/1, 4 | local_alias_to_recursive_type/1, 5 | remote/1, 6 | generic_with_remote_opaque/1, 7 | remote_opaque/1, 8 | remote_record_variants/1]). 9 | 10 | -type alias_t() :: exhaustive_user_type:t(). 11 | -type recursive_t() :: exhaustive_user_type:recursive_t(). 12 | 13 | -spec local_alias(alias_t()) -> ok. 14 | local_alias(T) -> 15 | case T of 16 | {true, _} -> ok 17 | end. 18 | 19 | -spec local_alias_to_recursive_type(recursive_t()) -> ok. 20 | local_alias_to_recursive_type(T) -> 21 | case T of 22 | ok -> ok 23 | end. 24 | 25 | -spec remote(exhaustive_user_type:t()) -> ok. 26 | remote(T) -> 27 | case T of 28 | {true, _} -> ok 29 | end. 30 | 31 | -type g(T) :: ok | {generic, T}. 32 | 33 | -spec generic_with_remote_opaque(g(exhaustive_user_type:opaque_t())) -> ok. 34 | generic_with_remote_opaque(T) -> 35 | case T of 36 | ok -> ok 37 | end. 38 | 39 | -spec remote_opaque(exhaustive_user_type:opaque_t()) -> ok. 40 | remote_opaque(T) -> 41 | case T of 42 | left -> ok 43 | end. 44 | 45 | -include("exhaustive_user_type.hrl"). 46 | 47 | -spec remote_record_variants(exhaustive_user_type:record_sum_t()) -> ok. 48 | remote_record_variants(T) -> 49 | case T of 50 | #variant1{} -> ok 51 | end. 52 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_string_variants.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_string_variants). 2 | 3 | -export([string_variants/1]). 4 | 5 | -type string_variant_t() :: {non_string, integer()} 6 | | {string, string()}. 7 | 8 | -spec string_variants(string_variant_t()) -> ok. 9 | string_variants(T) -> 10 | case T of 11 | {non_string, _} -> ok 12 | end. 13 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_type.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_type). 2 | 3 | -export([allergen_score/1]). 4 | 5 | -type allergen() :: eggs 6 | | chocolate 7 | | pollen 8 | | cats. 9 | 10 | -spec allergen_score(allergen()) -> integer(). 11 | allergen_score(Al) -> 12 | case Al of 13 | eggs -> 1; 14 | chocolate -> 32; 15 | pollen -> 64 16 | end. 17 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_user_type.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustive_user_type). 2 | 3 | -export([simple/1, 4 | recursive/1, 5 | mutually_recursive/1, 6 | generic/1, 7 | local_opaque/1, 8 | generic_with_local_opaque/1, 9 | record_variants/1]). 10 | 11 | -export_type([t/0, 12 | recursive_t/0, 13 | opaque_t/0, 14 | record_sum_t/0, 15 | map_sum_t/0]). 16 | 17 | -type info() :: integer(). 18 | 19 | -type t() :: {true, info()} 20 | | {false, info()}. 21 | 22 | -spec simple(t()) -> ok. 23 | simple(T) -> 24 | case T of 25 | {true, _} -> ok 26 | end. 27 | 28 | -type recursive_t() :: ok | {r, recursive_t()}. 29 | 30 | -spec recursive(recursive_t()) -> ok. 31 | recursive(T) -> 32 | case T of 33 | ok -> ok 34 | end. 35 | 36 | -type mutually_recursive1_t() :: ok | {mr1, mutually_recursive2_t()}. 37 | -type mutually_recursive2_t() :: ok | {mr2, mutually_recursive1_t()}. 38 | 39 | -spec mutually_recursive(mutually_recursive1_t()) -> ok. 40 | mutually_recursive(T) -> 41 | case T of 42 | ok -> ok 43 | end. 44 | 45 | -type g(T) :: ok | {generic, T}. 46 | 47 | -spec generic(g(integer())) -> ok. 48 | generic(T) -> 49 | case T of 50 | ok -> ok 51 | end. 52 | 53 | -opaque opaque_t() :: left | right. 54 | 55 | -spec local_opaque(opaque_t()) -> ok. 56 | local_opaque(T) -> 57 | case T of 58 | left -> ok 59 | end. 60 | 61 | -spec generic_with_local_opaque(g(opaque_t())) -> ok. 62 | generic_with_local_opaque(T) -> 63 | case T of 64 | ok -> ok 65 | end. 66 | 67 | -include("exhaustive_user_type.hrl"). 68 | 69 | -type record_sum_t() :: #variant1{} 70 | | #variant2{}. 71 | 72 | -spec record_variants(record_sum_t()) -> ok. 73 | record_variants(T) -> 74 | case T of 75 | #variant1{} -> ok 76 | end. 77 | 78 | -type map_sum_t() :: #{field_one := _} 79 | | #{field_two := _}. 80 | 81 | %% See test/known_problems/should_fail/exhaustive_map_variants.erl 82 | %-spec map_variants(map_sum_t()) -> ok. 83 | %map_variants(T) -> 84 | % case T of 85 | % #{field_one := _} -> ok 86 | % end. 87 | -------------------------------------------------------------------------------- /test/should_fail/exhaustive_user_type.hrl: -------------------------------------------------------------------------------- 1 | -record(variant1, {a}). 2 | -record(variant2, {b}). 3 | -------------------------------------------------------------------------------- /test/should_fail/exhaustiveness_check_toggling.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustiveness_check_toggling). 2 | 3 | -export([f/1]). 4 | 5 | -type adt() :: {case_a, string()} 6 | | {case_b, integer()} 7 | | case_c. 8 | 9 | -spec f(adt()) -> atom(). 10 | f(ADT) -> 11 | case ADT of 12 | {case_a, List} -> 13 | case List of 14 | %% This pattern should disable exhaustiveness checking for the inner case expr. 15 | [$a | _] -> 16 | nonexhaustive_list_pattern; 17 | [] -> 18 | ok 19 | end; 20 | {case_b, _} -> 21 | ok 22 | %% But here, in the outer case expr, case_c is not matched on. 23 | %% It should be reported as not exhausted by the outer case expr. 24 | end. 25 | -------------------------------------------------------------------------------- /test/should_fail/generator.erl: -------------------------------------------------------------------------------- 1 | -module(generator). 2 | 3 | -export([f/1, g/1]). 4 | 5 | -spec f(integer()) -> list(). 6 | f(N) -> 7 | [ X || X <- N]. 8 | 9 | -spec g(integer()) -> list(). 10 | g(N) -> 11 | L = [ X || X <- N], 12 | L. 13 | -------------------------------------------------------------------------------- /test/should_fail/guard_fail.erl: -------------------------------------------------------------------------------- 1 | -module(guard_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec wrong_guard(integer() | atom()) -> atom() | not_atom. 6 | wrong_guard(A) when is_integer(A) -> A; 7 | wrong_guard(_) -> not_atom. 8 | 9 | -record(r1, { 10 | f 11 | }). 12 | 13 | -record(r2, { 14 | f 15 | }). 16 | 17 | -spec wrong_record(#r1{} | #r2{}) -> #r2{} | not_r2. 18 | wrong_record(R) when is_record(R, r1) -> R; 19 | wrong_record(_) -> not_r2. 20 | 21 | -spec wrong_union(integer() | float() | atom()) -> integer() | float() | not_number. 22 | wrong_union(I) when is_integer(I) -> I; 23 | wrong_union(F) when is_atom(F) -> F; 24 | wrong_union(_) -> not_number. 25 | 26 | -spec wrong_union2(integer() | float() | atom()) -> integer() | float() | not_number. 27 | wrong_union2(N) when is_integer(N) orelse is_atom(N) -> N; 28 | wrong_union2(_) -> not_number. 29 | 30 | -spec wrong_orelse(integer() | atom(), integer() | atom()) -> integer(). 31 | wrong_orelse(X, Y) when is_integer(X) orelse is_integer(Y) -> X + Y; 32 | wrong_orelse(_, _) -> 42. 33 | 34 | %% We currently don't infer X :: nonempty_list() from hd(X) 35 | -spec other_guard_fun([true, ...] | boo) -> boolean(). 36 | other_guard_fun(X) when is_list(X); hd(X) -> length(X) > 3; 37 | other_guard_fun(_) -> false. 38 | 39 | -spec compare_still_float(number()) -> list(). 40 | compare_still_float(N) when 1 =< N, 10 >= N -> 41 | integer_to_list(N); % error: N can still be a float 42 | compare_still_float(_) -> 43 | "X". 44 | 45 | -spec equal_still_float(number()) -> list(). 46 | equal_still_float(N) when 0 == N -> 47 | integer_to_list(N); % error: N can still be float, since 0.0 == 0 48 | equal_still_float(_) -> 49 | "X". 50 | -------------------------------------------------------------------------------- /test/should_fail/imported_undef.erl: -------------------------------------------------------------------------------- 1 | -module(imported_undef). 2 | 3 | -export([foo/0]). 4 | -import(any, [undef_foo/0]). 5 | 6 | -spec foo() -> ok. 7 | foo() -> undef_foo(). 8 | -------------------------------------------------------------------------------- /test/should_fail/infer_enabled.erl: -------------------------------------------------------------------------------- 1 | -module(infer_enabled). 2 | 3 | -export([f/0]). 4 | 5 | f() -> 6 | X = 1, Y = banana, 7 | X + Y. 8 | -------------------------------------------------------------------------------- /test/should_fail/intersection_check.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_check). 2 | 3 | -export([h/0]). 4 | 5 | -spec f(integer()) -> integer(); 6 | (boolean()) -> boolean(). 7 | f(X) -> 8 | X. 9 | 10 | -spec h() -> {}. 11 | h() -> 12 | f(false). 13 | -------------------------------------------------------------------------------- /test/should_fail/intersection_fail.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_fail). 2 | 3 | -export([k/1]). 4 | 5 | -spec i(a, b) -> {a, b}; 6 | (d, e) -> {d, e}. 7 | i(V, U) -> {V, U}. 8 | 9 | -spec k({a, e} | {d, b}) -> {a, e} | {d, b}. 10 | k({V, U}) -> i(V, U). 11 | -------------------------------------------------------------------------------- /test/should_fail/intersection_infer.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_infer). 2 | 3 | -export([g/0]). 4 | 5 | -spec f(integer()) -> integer(); 6 | (boolean()) -> boolean(). 7 | f(X) -> 8 | X. 9 | 10 | g() -> 11 | f({}). 12 | -------------------------------------------------------------------------------- /test/should_fail/intersection_with_any_fail.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_with_any_fail). 2 | 3 | -export([intersection_using_constraints/1]). 4 | 5 | -spec intersection_using_constraints(X) -> atom() when X :: integer(), X :: any() . 6 | intersection_using_constraints(X) -> 7 | X. 8 | -------------------------------------------------------------------------------- /test/should_fail/iodata_fail.erl: -------------------------------------------------------------------------------- 1 | -module(iodata_fail). 2 | 3 | -export([utf/0]). 4 | 5 | %% an iodata can only contain bytes, no unicode code points above 255 6 | utf() -> 7 | %% the last character is hexadecimal 256 8 | %% (unicode "Latin Capital Letter a with Macron") 9 | expect_iodata("foo\x{100}"). 10 | 11 | -spec expect_iodata(iodata()) -> any(). 12 | expect_iodata(_IOData) -> 13 | ok. 14 | -------------------------------------------------------------------------------- /test/should_fail/lambda_not_fun.erl: -------------------------------------------------------------------------------- 1 | -module(lambda_not_fun). 2 | 3 | -export([bar/0]). 4 | 5 | -spec foo(integer()) -> integer(). 6 | foo(N) -> N. 7 | 8 | bar() -> foo(fun() -> 0 end). 9 | -------------------------------------------------------------------------------- /test/should_fail/lc_generator_not_none_fail.erl: -------------------------------------------------------------------------------- 1 | -module(lc_generator_not_none_fail). 2 | 3 | %% Compare with test/should_pass/lc_generator_not_none.erl 4 | 5 | -export([g/2]). 6 | 7 | -type t() :: {a, d} 8 | | {a, [t()]}. 9 | 10 | -spec g(integer(), t()) -> [t()]. 11 | g(1, {a, d} = T) -> 12 | [T]; 13 | g(_, {a, Ts}) -> 14 | [ T || T <- Ts ]; 15 | g(_, _) -> 16 | %% This clause is redundant, as the previous two exhaust g/2 parameters. 17 | %% It's correctly reported as such. 18 | []. 19 | -------------------------------------------------------------------------------- /test/should_fail/lc_not_list.erl: -------------------------------------------------------------------------------- 1 | -module(lc_not_list). 2 | 3 | -export([bar/0, baz/0]). 4 | 5 | -spec foo(integer()) -> integer(). 6 | foo(N) -> N. 7 | 8 | bar() -> foo([ X || X <- [1, 2]]). 9 | baz() -> foo(<< <> || X <- [1, 2] >>). 10 | -------------------------------------------------------------------------------- /test/should_fail/list_infer_fail.erl: -------------------------------------------------------------------------------- 1 | -module(list_infer_fail). 2 | 3 | -export([f/0]). 4 | 5 | f() -> 6 | V = [1, 2], 7 | g(V). 8 | 9 | -spec g(integer()) -> any(). 10 | g(Int) -> Int + 1. 11 | -------------------------------------------------------------------------------- /test/should_fail/list_op.erl: -------------------------------------------------------------------------------- 1 | -module(list_op). 2 | -export([append_right_error/1, 3 | append_left_error/1, 4 | subtract/1, 5 | subtract/2, 6 | subtract2/1, 7 | subtract2/2]). 8 | 9 | -spec append_right_error(integer()) -> _ | integer(). 10 | append_right_error(X) -> [1] ++ X. 11 | 12 | -spec append_left_error(integer()) -> _ | integer(). 13 | append_left_error(X) -> X ++ [1]. 14 | 15 | -spec subtract([a]) -> {ok, [a]}. 16 | subtract(Xs) -> Xs -- Xs. 17 | 18 | -spec subtract([a, ...], [a]) -> [a, ...]. 19 | subtract(Xs, Ys) -> Xs -- Ys. 20 | 21 | -spec subtract2([a]) -> {ok, [a]}. 22 | subtract2(Xs) -> 23 | A = Xs -- Xs, 24 | A. 25 | 26 | -spec subtract2([a, ...], [a]) -> [a, ...]. 27 | subtract2(Xs, Ys) -> 28 | A = Xs -- Ys, 29 | A. 30 | -------------------------------------------------------------------------------- /test/should_fail/list_op_should_fail.erl: -------------------------------------------------------------------------------- 1 | -module(list_op_should_fail). 2 | 3 | %% See also list_op_should_pass. 4 | 5 | -export([improper_concat_op_elem_gives_badarg/0, 6 | improper_concat_fun_elem_gives_badarg/0, 7 | improper_concat_op_nonempty_gives_badarg/0, 8 | improper_concat_fun_nonempty_gives_badarg/0]). 9 | 10 | -spec improper_concat_op_elem_gives_badarg() -> list(). 11 | improper_concat_op_elem_gives_badarg() -> 12 | [a|b] ++ c. 13 | 14 | -spec improper_concat_fun_elem_gives_badarg() -> list(). 15 | improper_concat_fun_elem_gives_badarg() -> 16 | erlang:'++'([a|b], c). 17 | 18 | -spec improper_concat_op_nonempty_gives_badarg() -> list(). 19 | improper_concat_op_nonempty_gives_badarg() -> 20 | [a|b] ++ [c]. 21 | 22 | -spec improper_concat_fun_nonempty_gives_badarg() -> list(). 23 | improper_concat_fun_nonempty_gives_badarg() -> 24 | erlang:'++'([a|b], [c]). 25 | -------------------------------------------------------------------------------- /test/should_fail/list_union_fail.erl: -------------------------------------------------------------------------------- 1 | -module(list_union_fail). 2 | -export([bar/0, baz/0]). 3 | 4 | -spec foo([a] | [b]) -> ok. 5 | foo(_) -> ok. 6 | 7 | %% Should fail since c has neither type a or b. Shouldn't crash. 8 | bar() -> foo([c]). 9 | 10 | %% Also for list comprehensions. 11 | baz() -> foo([c || _ <- [1, 2]]). 12 | -------------------------------------------------------------------------------- /test/should_fail/lists_map_nonempty_fail.erl: -------------------------------------------------------------------------------- 1 | -module(lists_map_nonempty_fail). 2 | 3 | -export([f/1]). 4 | 5 | -spec f([A]) -> [A, ...]. 6 | f(L) -> 7 | lists:map(fun (El) -> El end, L). 8 | -------------------------------------------------------------------------------- /test/should_fail/literal_char.erl: -------------------------------------------------------------------------------- 1 | -module(literal_char). 2 | 3 | -export([f/0]). 4 | 5 | -spec f() -> ok. 6 | f() -> $c. 7 | -------------------------------------------------------------------------------- /test/should_fail/literal_patterns.erl: -------------------------------------------------------------------------------- 1 | -module(literal_patterns). 2 | 3 | -export([f/1, f/2, g/1, h/1, i/1]). 4 | 5 | -spec f(not_a_number) -> not_a_number. 6 | f(10 = X) -> X. 7 | 8 | -spec f(_, string()) -> ok. 9 | f(_, $a) -> ok. 10 | 11 | -spec g({ok, 10..20}) -> ok. 12 | g({ok, 21}) -> ok. 13 | 14 | -spec h(integer()) -> ok. 15 | h(1.0) -> ok. 16 | 17 | -spec i(atom) -> ok. 18 | i("foo") -> ok. 19 | -------------------------------------------------------------------------------- /test/should_fail/logic_op.erl: -------------------------------------------------------------------------------- 1 | -module(logic_op). 2 | -export([failand/1, failand2/1, 3 | failandleft/2, failandleft2/2, 4 | failandright/2, failandright2/2, 5 | failandalso/1, failandalso2/1, 6 | failnot/1]). 7 | 8 | -spec failand(boolean()) -> tuple(). 9 | failand(X) -> X and X. 10 | 11 | -spec failand2(boolean()) -> tuple(). 12 | failand2(X) -> 13 | O = X and X, 14 | O. 15 | 16 | -spec failandleft(boolean(), integer()) -> boolean(). 17 | failandleft(B, N) -> N and B. 18 | 19 | -spec failandleft2(boolean(), integer()) -> boolean(). 20 | failandleft2(B, N) -> 21 | O = N and B, 22 | O. 23 | 24 | -spec failandright(boolean(), integer()) -> boolean(). 25 | failandright(B, N) -> B and N. 26 | 27 | -spec failandright2(boolean(), integer()) -> boolean(). 28 | failandright2(B, N) -> 29 | O = B and N, 30 | O. 31 | 32 | -spec failandalso(boolean()) -> tuple(). 33 | failandalso(X) -> X andalso {}. 34 | 35 | -spec failandalso2(boolean()) -> tuple(). 36 | failandalso2(X) -> 37 | O = X andalso {}, 38 | O. 39 | 40 | -spec failnot(integer()) -> boolean(). 41 | failnot(N) -> 42 | O = not(N), 43 | O. 44 | -------------------------------------------------------------------------------- /test/should_fail/map_entry.erl: -------------------------------------------------------------------------------- 1 | -module(map_entry). 2 | -export([f/0, g/0]). 3 | 4 | -spec f() -> #{bepa := apa}. 5 | f() -> #{apa => bepa}. 6 | 7 | -type typed_map() :: #{field_a := atom()}. 8 | 9 | -spec g() -> typed_map(). 10 | g() -> 11 | #{field_a => <<"ala ma kota">>}. 12 | -------------------------------------------------------------------------------- /test/should_fail/map_fail.erl: -------------------------------------------------------------------------------- 1 | -module(map_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec mandatory_fail(#{a => b}) -> #{a := b}. 6 | mandatory_fail(M) -> M. 7 | -------------------------------------------------------------------------------- /test/should_fail/map_failing_expr.erl: -------------------------------------------------------------------------------- 1 | -module(map_failing_expr). 2 | 3 | -export([foo/0, bar/0]). 4 | 5 | -spec foo() -> ok. 6 | foo() -> 7 | #{ok => 42}. 8 | 9 | -spec bar() -> ok. 10 | bar() -> 11 | A = #{ok => 42}, 12 | A. 13 | -------------------------------------------------------------------------------- /test/should_fail/map_failing_subtyping.erl: -------------------------------------------------------------------------------- 1 | -module(map_failing_subtyping). 2 | 3 | -export([m1/0, m2/0]). 4 | 5 | -spec m1() -> #{bar => optional}. 6 | m1() -> 7 | #{foo => bar}. 8 | 9 | -spec m2() -> #{foo := atom(), bar := any()}. 10 | m2() -> 11 | m(). 12 | 13 | -spec m() -> #{foo := atom(), bar => optional}. 14 | m() -> 15 | #{foo => bar}. 16 | -------------------------------------------------------------------------------- /test/should_fail/map_field_invalid_update.erl: -------------------------------------------------------------------------------- 1 | -module(map_field_invalid_update). 2 | 3 | -export([f/1, g/1]). 4 | 5 | -spec f(#{a := integer()}) -> #{a := binary()}. 6 | f(#{} = Ctx) -> 7 | Ctx#{a := not_a_binary}. 8 | 9 | %% Ctx might not have a field `a`, but Ctx2 has for sure (mandatory) 10 | -spec g(map()) -> #{a := any()}. 11 | g(Ctx) -> 12 | Ctx2 = Ctx#{a => 5}, 13 | Ctx2. 14 | -------------------------------------------------------------------------------- /test/should_fail/map_literal.erl: -------------------------------------------------------------------------------- 1 | -module(map_literal). 2 | -export([f/0]). 3 | 4 | -spec f() -> ok. 5 | f() -> #{apa => bepa}. 6 | -------------------------------------------------------------------------------- /test/should_fail/map_pattern_fail.erl: -------------------------------------------------------------------------------- 1 | -module(map_pattern_fail). 2 | -export([f/1, 3 | badkey/1, 4 | map_term/1, 5 | not_a_map_passed_as_map/0, 6 | wrong_spec/1 7 | ]). 8 | 9 | -type t() :: #{apa := integer(), bepa := boolean()}. 10 | 11 | -spec f(t()) -> string(). 12 | f(#{bepa := Bepa}) -> 13 | Bepa. 14 | 15 | -spec badkey(#{apa => atom()}) -> ok. 16 | badkey(#{bepa := _Bepa}) -> ok. 17 | 18 | -spec map_term(gradualizer:top()) -> any(). 19 | map_term(#{k := V}) -> 20 | %% at this point V :: term() 21 | atom_to_list(V). 22 | 23 | not_a_map_passed_as_map() -> 24 | G = g(), 25 | h(G). 26 | 27 | -spec g() -> {tup, le}. 28 | g() -> {tup, le}. 29 | 30 | -spec h(map()) -> ok. 31 | h(#{k := v} = _Map) -> 32 | ok. 33 | 34 | -spec wrong_spec(tuple()) -> ok. 35 | wrong_spec(#{k := v} = _Map) -> 36 | ok. 37 | -------------------------------------------------------------------------------- /test/should_fail/map_type_error.erl: -------------------------------------------------------------------------------- 1 | -module(map_type_error). 2 | 3 | -export([nomap/0]). 4 | 5 | -spec nomap() -> integer(). 6 | nomap() -> #{fruit => banana}. 7 | -------------------------------------------------------------------------------- /test/should_fail/match.erl: -------------------------------------------------------------------------------- 1 | -module(match). 2 | -export([fail/0]). 3 | 4 | -spec foo() -> integer(). 5 | foo() -> 0. 6 | 7 | fail() -> X = [X = foo()]. 8 | -------------------------------------------------------------------------------- /test/should_fail/messaging_fail.erl: -------------------------------------------------------------------------------- 1 | -module(messaging_fail). 2 | 3 | -export([server1/0, server2/0, server3/0, server4/0]). 4 | 5 | -spec ok() -> ok. 6 | ok() -> ok. 7 | 8 | -spec nok() -> nok. 9 | nok() -> nok. 10 | 11 | -spec server1() -> nok. 12 | server1() -> 13 | receive 14 | quit -> 15 | ok(); 16 | Foo -> 17 | io:format("~p~n", [Foo]), 18 | server1() 19 | end. 20 | 21 | -spec server2() -> nok. 22 | server2() -> 23 | A = receive 24 | quit -> 25 | ok(); 26 | Foo -> 27 | io:format("~p~n", [Foo]), 28 | server2() 29 | end, 30 | A. 31 | 32 | -spec server3() -> nok. 33 | server3() -> 34 | receive 35 | quit -> 36 | nok(); 37 | Foo -> 38 | io:format("~p~n", [Foo]), 39 | server3() 40 | after 1000 -> ok() 41 | end. 42 | 43 | -spec server4() -> nok. 44 | server4() -> 45 | A = receive 46 | quit -> 47 | nok(); 48 | Foo -> 49 | io:format("~p~n", [Foo]), 50 | server4() 51 | after 1000 -> ok() 52 | end, 53 | A. 54 | -------------------------------------------------------------------------------- /test/should_fail/module_info_fail.erl: -------------------------------------------------------------------------------- 1 | -module(module_info_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec md5() -> atom(). 6 | md5() -> 7 | erlang:module_info(md5). -------------------------------------------------------------------------------- /test/should_fail/named_fun_fail.erl: -------------------------------------------------------------------------------- 1 | -module(named_fun_fail). 2 | 3 | -export([bar/0, baz/1, sum/1]). 4 | 5 | -spec foo(integer()) -> integer(). 6 | foo(N) -> N. 7 | 8 | bar() -> foo(fun F(0) -> 0; F(X) -> F(X - 1) end). 9 | 10 | -spec baz(integer()) -> boolean(). 11 | baz(I) -> 12 | O = I({}), 13 | O. 14 | 15 | -spec sum([integer()]) -> integer(). 16 | sum(Ints) -> 17 | F = fun Sum(Acc, [Int | Rest]) -> 18 | Sum(Acc + Int, Rest); 19 | Sum(Acc, []) -> 20 | Acc 21 | end, 22 | F(Ints). 23 | -------------------------------------------------------------------------------- /test/should_fail/named_fun_infer_fail.erl: -------------------------------------------------------------------------------- 1 | -module(named_fun_infer_fail). 2 | 3 | -export([bar/0, sum/1]). 4 | 5 | -spec foo(integer()) -> integer(). 6 | foo(N) -> N. 7 | 8 | bar() -> 9 | A = fun F(0) -> 0; F(X) -> F(X - 1) end, 10 | foo(A). 11 | 12 | -spec sum([integer()]) -> integer(). 13 | sum(Ints) -> 14 | F = fun Sum(Acc, [Int | Rest]) -> 15 | Sum(Acc + Int, Rest); 16 | Sum(Acc, []) -> 17 | Acc 18 | end, 19 | F(Ints). 20 | -------------------------------------------------------------------------------- /test/should_fail/nil.erl: -------------------------------------------------------------------------------- 1 | -module(nil). 2 | -export([f/0]). 3 | 4 | -spec f() -> nonempty_list(). 5 | f() -> []. 6 | -------------------------------------------------------------------------------- /test/should_fail/no_idempotent_xor.erl: -------------------------------------------------------------------------------- 1 | -module(no_idempotent_xor). 2 | -export([bla/2]). 3 | 4 | -spec bla(true, true) -> _. 5 | bla(X, Y) -> 6 | Z = X xor Y, %% We should not infer Z :: true 7 | zz(Z). 8 | 9 | -spec zz(true) -> true. 10 | zz(X) -> X. 11 | -------------------------------------------------------------------------------- /test/should_fail/non_neg_plus_pos_is_pos_fail.erl: -------------------------------------------------------------------------------- 1 | -module(non_neg_plus_pos_is_pos_fail). 2 | 3 | -export([f/1, g/1]). 4 | 5 | -spec f(neg_integer()) -> pos_integer(). 6 | f(N) -> 7 | h(N + 1). 8 | 9 | -spec g(neg_integer()) -> pos_integer(). 10 | g(N) -> 11 | h(1 + N). 12 | 13 | -spec h(pos_integer()) -> pos_integer(). 14 | h(P) -> P. 15 | -------------------------------------------------------------------------------- /test/should_fail/nonempty_list_match_in_head_nonexhaustive.erl: -------------------------------------------------------------------------------- 1 | -module(nonempty_list_match_in_head_nonexhaustive). 2 | 3 | -export([f/1]). 4 | 5 | -type t() :: {} | [t()]. 6 | 7 | -spec f(t()) -> ok. 8 | f(_A = [_|_]) -> ok. 9 | -------------------------------------------------------------------------------- /test/should_fail/nonempty_string_fail.erl: -------------------------------------------------------------------------------- 1 | -module(nonempty_string_fail). 2 | 3 | -export([empty_as_nonempty_string/0]). 4 | 5 | -spec empty_as_nonempty_string() -> nonempty_string(). 6 | empty_as_nonempty_string() -> "". 7 | -------------------------------------------------------------------------------- /test/should_fail/opaque_fail.erl: -------------------------------------------------------------------------------- 1 | -module(opaque_fail). 2 | 3 | -export([use_external/2, update_without_opaque/0, add_to_opaque/0, return_opaque/0]). 4 | 5 | -spec use_external(user_types:my_opaque(), integer() | undefined) -> integer(). 6 | use_external(I, undefined) -> I; 7 | use_external(_, I) -> I. 8 | 9 | -spec update_without_opaque() -> ok. 10 | update_without_opaque() -> 11 | _Val = user_types:update_opaque(3), 12 | ok. 13 | 14 | -spec add_to_opaque() -> ok. 15 | add_to_opaque() -> 16 | Val = user_types:new_opaque(), 17 | Val + 1, 18 | ok. 19 | 20 | -spec return_opaque() -> user_types:my_opaque(). 21 | return_opaque() -> 3. 22 | -------------------------------------------------------------------------------- /test/should_fail/operator_pattern_fail.erl: -------------------------------------------------------------------------------- 1 | -module(operator_pattern_fail). 2 | -export([n/1, p/1]). 3 | 4 | -spec n(non_neg_integer()) -> {}. 5 | n(1-2) -> {}. 6 | 7 | -spec p(pos_integer()) -> {}. 8 | p(1-1) -> {}. 9 | -------------------------------------------------------------------------------- /test/should_fail/pattern.erl: -------------------------------------------------------------------------------- 1 | -module(pattern). 2 | 3 | -export([pattern_test/1]). 4 | 5 | -spec pattern_test(integer()) -> {}. 6 | pattern_test(1) -> 7 | true; 8 | pattern_test(_) -> 9 | {}. 10 | -------------------------------------------------------------------------------- /test/should_fail/pattern_record_fail.erl: -------------------------------------------------------------------------------- 1 | -module(pattern_record_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -record(r1, { 6 | f 7 | }). 8 | 9 | -record(r2, { 10 | f 11 | }). 12 | 13 | -spec bad_match1(#r1{} | other) -> #r1{} | not_r1. 14 | bad_match1(R = #r2{}) -> R; 15 | bad_match1(_) -> not_r1. 16 | 17 | -spec bad_match2(#r1{} | #r2{}) -> #r1{} | not_r1. 18 | bad_match2(R = #r2{}) -> R; 19 | bad_match2(_) -> not_r1. 20 | 21 | -spec fail(#r1{} | #r2{}) -> integer(). 22 | fail(R = #r2{f = F}) -> R#r1.f + F; 23 | fail(_) -> 0. 24 | 25 | -------------------------------------------------------------------------------- /test/should_fail/poly_lists_map_fail.erl: -------------------------------------------------------------------------------- 1 | -module(poly_lists_map_fail). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([g/1, 6 | h/1]). 7 | 8 | -spec g([integer()]) -> [atom()]. 9 | g(L) -> 10 | lists:map(fun times_two/1, L). 11 | 12 | -spec h([string()]) -> [integer()]. 13 | h(L) -> 14 | lists:map(fun times_two/1, L). 15 | 16 | -spec times_two(integer()) -> integer(). 17 | times_two(N) -> N * 2. 18 | -------------------------------------------------------------------------------- /test/should_fail/poly_union_lower_bound_fail.erl: -------------------------------------------------------------------------------- 1 | -module(poly_union_lower_bound_fail). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([i/1, 6 | j/1, 7 | k/1, 8 | l/1]). 9 | 10 | -spec i([binary() | integer()]) -> [integer()]. 11 | i(L) -> 12 | lists:map(fun erlang:integer_to_list/1, L). 13 | 14 | %% We expect an error like: 15 | %% 16 | %% Lower bound binary() | integer() | string() of type variable T_123 17 | %% on line 456 is not a subtype of binary() | integer() 18 | -spec j([binary() | integer() | string()]) -> [integer()]. 19 | j(L) -> 20 | lists:map(fun takes_a_union/1, L). 21 | -spec takes_a_union(binary() | integer()) -> integer(). 22 | takes_a_union(B) when is_binary(B) -> binary_to_integer(B); 23 | takes_a_union(I) when is_integer(I) -> I. 24 | 25 | %% We expect an error like: 26 | %% 27 | %% Lower bound binary() | integer() | string() of type variable T_123 28 | %% on line 456 is not a subtype of binary() | integer() 29 | -spec k([binary() | integer() | string()]) -> list(). 30 | k(L) -> 31 | lists:map(fun has_intersection_spec/1, L). 32 | 33 | -spec has_intersection_spec(binary()) -> binary(); 34 | (integer()) -> integer(). 35 | has_intersection_spec(B) when is_binary(B) -> B; 36 | has_intersection_spec(I) when is_integer(I) -> I. 37 | 38 | -spec l([binary() | integer()]) -> [integer()]. 39 | l(L) -> 40 | lists:map(fun has_intersection_spec/1, L). 41 | -------------------------------------------------------------------------------- /test/should_fail/pp_intersection.erl: -------------------------------------------------------------------------------- 1 | -module(pp_intersection). 2 | -export([bar/1]). 3 | 4 | %% Mostly to make sure pretty printing intersection types doesn't crash. 5 | 6 | -spec cast(_) -> _. 7 | cast(X) -> X. 8 | 9 | -spec foo(integer()) -> float(); 10 | (Float) -> integer() when Float :: float(). 11 | foo(N) when is_integer(N) -> cast(N * 1.0); 12 | foo(X) when is_float(X) -> cast(round(X)). 13 | 14 | -spec bar(boolean()) -> boolean(). 15 | bar(X) -> foo(X). 16 | 17 | -------------------------------------------------------------------------------- /test/should_fail/record.erl: -------------------------------------------------------------------------------- 1 | -module(record). 2 | 3 | -export([g/0, h/0]). 4 | 5 | -record(rec, { apa :: integer()}). 6 | 7 | -spec g() -> integer(). 8 | g() -> 9 | #rec{apa = 1}. 10 | 11 | -spec h() -> integer(). 12 | h() -> 13 | Rec = #rec{}, 14 | Rec#rec.apa. 15 | -------------------------------------------------------------------------------- /test/should_fail/record_exhaustive.erl: -------------------------------------------------------------------------------- 1 | -module(record_exhaustive). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -record(inner, { 6 | field :: integer() 7 | }). 8 | 9 | -record(record_one_field, { 10 | inner_rec :: #inner{} | undefined 11 | }). 12 | 13 | -record(record_two_fields, { 14 | a :: #record_one_field{}, 15 | b :: integer() | undefined 16 | }). 17 | 18 | %% expected error message in the console: 19 | %% Example values which are not covered: 20 | %% #record_one_field{ 21 | %% inner_rec = undefined 22 | %% } 23 | -spec one_field(#record_one_field{} | undefined) -> integer(). 24 | one_field(#record_one_field{inner_rec = InnerRec = #inner{}}) -> InnerRec#inner.field; 25 | %% unhandled 26 | %%one_field(#record_one_field{field = undefined}) -> -1; 27 | one_field(undefined) -> 0. 28 | 29 | %% expected error message in the console: 30 | %% Example values which are not covered: 31 | %% #record_two_fields{ 32 | %% a = #record_one_field{ 33 | %% inner_rec = undefined 34 | %% }, 35 | %% b = 0 36 | %% } 37 | -spec two_fields(#record_two_fields{} | undefined) -> integer(). 38 | two_fields(#record_two_fields{a = #record_one_field{inner_rec = InnerRec = #inner{}}}) -> InnerRec#inner.field; 39 | %% unhandled 40 | %%two_fields(#record_two_fields{a = #record_one_field{inner_rec = undefined}}) -> 2; 41 | two_fields(#record_two_fields{b = undefined}) -> 0; 42 | %% unhandled 43 | %%two_fields(#record_two_fields{b = B}) -> B; 44 | two_fields(undefined) -> 0. 45 | 46 | 47 | 48 | %% expected error message in the console: 49 | %% Example values which are not covered: 50 | %% #union_rec{ 51 | %% foo = c 52 | %% bar = -1 53 | %% } 54 | -record(union_rec, { 55 | foo :: a | b | c, 56 | bar :: float() %% non-refinable field 57 | }). 58 | -spec union_rec(#union_rec{}) -> integer(). 59 | union_rec(#union_rec{foo = a}) -> 0; 60 | union_rec(#union_rec{foo = b}) -> 1. 61 | %% unhandled 62 | %%union_rec(#union_rec{foo = c}) -> 2. 63 | 64 | %% expected error message: 65 | %% Example values which are not covered: 66 | %% #empty_rec{} 67 | -record(empty_rec, {}). 68 | -spec empty_rec(#empty_rec{} | undefined) -> integer(). 69 | empty_rec(undefined) -> 1. 70 | -------------------------------------------------------------------------------- /test/should_fail/record_field.erl: -------------------------------------------------------------------------------- 1 | -module(record_field). 2 | 3 | -export([f/1, g/1]). 4 | 5 | -record(rec, { apa :: integer()}). 6 | 7 | -spec f(#rec{}) -> boolean(). 8 | f(A) -> 9 | A#rec.apa. 10 | 11 | -spec g(#rec{}) -> pos_integer(). 12 | g(A) -> 13 | A#rec.apa. 14 | -------------------------------------------------------------------------------- /test/should_fail/record_index.erl: -------------------------------------------------------------------------------- 1 | -module(record_index). 2 | 3 | -export([f/0, g/0]). 4 | 5 | -record(rec, { apa :: integer()}). 6 | 7 | -spec f() -> boolean(). 8 | f() -> 9 | #rec.apa. 10 | 11 | -spec g() -> 3. 12 | g() -> 13 | #rec.apa. 14 | -------------------------------------------------------------------------------- /test/should_fail/record_info_fail.erl: -------------------------------------------------------------------------------- 1 | -module(record_info_fail). 2 | 3 | -export([fields/0, size/0]). 4 | 5 | -record(fruitz, {ananas, bananas :: atom(), cananas = 2}). 6 | 7 | -spec fields() -> [ananas | bananas]. 8 | fields() -> record_info(fields, fruitz). 9 | 10 | -spec size() -> 3. 11 | size() -> record_info(size, fruitz). 12 | -------------------------------------------------------------------------------- /test/should_fail/record_refinement_fail.erl: -------------------------------------------------------------------------------- 1 | -module(record_refinement_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -record(one_field, {a :: integer() | undefined}). 6 | 7 | -spec one_field(#one_field{}, integer()) -> integer(). 8 | one_field(#one_field{a = I}, _) -> I; 9 | one_field(_, I) -> I. 10 | 11 | -spec one_field2(#one_field{}, integer()) -> integer(). 12 | one_field2(R, _) -> R#one_field.a; 13 | one_field2(_, I) -> I. 14 | 15 | -record(refined_field, {f :: integer() | undefined}). 16 | -spec refined_field(#refined_field{}) -> #refined_field{f :: integer()}. 17 | refined_field(R) -> R. 18 | 19 | %% The refinement in the result type is not handled by Dialyzer - it will error out. 20 | %% Comment it out if trying to run `make dialyze-tests'. 21 | -spec refined_field2(#refined_field{}) -> #refined_field{f :: atom()}. 22 | refined_field2(#refined_field{f = undefined}) -> #refined_field{f = 0}; 23 | refined_field2(R) -> R. 24 | 25 | -record(two_level2, {value :: undefined | binary()}). 26 | -record(two_level1, {two_level2 :: undefined | #two_level2{}}). 27 | 28 | -spec two_level1(#two_level1{}) -> integer(). 29 | two_level1(#two_level1{two_level2 = undefined}) -> 30 | 0; 31 | two_level1(#two_level1{two_level2 = #two_level2{value = undefined}}) -> 32 | 0; 33 | two_level1(#two_level1{two_level2 = #two_level2{value = Value}}) -> 34 | Value. 35 | 36 | -spec two_level2(#two_level1{}) -> integer(). 37 | two_level2(#two_level1{two_level2 = undefined}) -> 38 | 0; 39 | two_level2(#two_level1{two_level2 = #two_level2{value = undefined}}) -> 40 | 0; 41 | two_level2(R1) -> 42 | R1#two_level1.two_level2#two_level2.value. 43 | -------------------------------------------------------------------------------- /test/should_fail/record_update.erl: -------------------------------------------------------------------------------- 1 | -module(record_update). 2 | 3 | -export([f/1]). 4 | 5 | -record(rec, { apa :: integer()}). 6 | 7 | -spec f(#rec{}) -> boolean(). 8 | f(A) -> 9 | A#rec{apa = 3}. 10 | -------------------------------------------------------------------------------- /test/should_fail/record_wildcard_fail.erl: -------------------------------------------------------------------------------- 1 | -module(record_wildcard_fail). 2 | 3 | -export([f/0, g/0]). 4 | 5 | -record(rec, {apa = 1 :: integer() 6 | ,bepa = false :: boolean() 7 | ,cepa = undefined :: atom() 8 | ,depa = 1.0 :: float() 9 | }). 10 | 11 | f() -> 12 | #rec{ apa = 1 13 | , _ = true }. 14 | 15 | -spec g() -> #rec{}. 16 | g() -> 17 | #rec{ apa = 1 18 | , _ = true }. 19 | -------------------------------------------------------------------------------- /test/should_fail/recursive_type_fail.erl: -------------------------------------------------------------------------------- 1 | -module(recursive_type_fail). 2 | 3 | -export([g/0, j/0, m/0]). 4 | 5 | -type recursive_t1() :: #{key => recursive_t1()}. 6 | 7 | -type recursive_t2() :: #{binary() | atom() => recursive_t2()} | true | false | null. 8 | 9 | -type recursive_t3() :: #{binary() | atom() => recursive_t3()} 10 | | true | false | null 11 | | integer() | float() 12 | | binary() | atom() 13 | | calendar:datetime(). 14 | 15 | -spec f() -> recursive_t1(). 16 | f() -> 17 | g(). 18 | 19 | -spec g() -> recursive_t1(). 20 | g() -> 21 | []. 22 | 23 | -spec i() -> recursive_t2(). 24 | i() -> 25 | j(). 26 | 27 | -spec j() -> recursive_t2(). 28 | j() -> 29 | []. 30 | 31 | -spec l() -> recursive_t3(). 32 | l() -> 33 | m(). 34 | 35 | -spec m() -> recursive_t3(). 36 | m() -> 37 | []. 38 | -------------------------------------------------------------------------------- /test/should_fail/recursive_types_failing.erl: -------------------------------------------------------------------------------- 1 | -module(recursive_types_failing). 2 | 3 | -export([recursive_param2/1]). 4 | 5 | -type rec2() :: rec2 | {rec2, rec2()} | {rec2, {rec2, rec2()}}. 6 | 7 | %% `{_, Z}' doesn't match on atom `rec2', which is a valid value of type `rec2()'. 8 | %% This should fail. 9 | -spec recursive_param2(rec2()) -> rec2(). 10 | recursive_param2({_, Z}) -> Z. -------------------------------------------------------------------------------- /test/should_fail/rel_op.erl: -------------------------------------------------------------------------------- 1 | -module(rel_op). 2 | -export([fail/1, fail/2, foo/2, bar/2]). 3 | 4 | -spec fail(term()) -> tuple(). 5 | fail(X) -> X > X. 6 | 7 | -spec fail(a | b, b | c) -> boolean() | tuple(). 8 | fail(X, Y) -> X == Y. 9 | 10 | -spec foo(atom(), atom()) -> integer(). 11 | foo(A, B) -> 12 | A = A < B, 13 | A. 14 | 15 | -spec bar(atom(), float()) -> boolean(). 16 | bar(A, B) -> 17 | A = A < B, 18 | A. 19 | -------------------------------------------------------------------------------- /test/should_fail/return_fun_fail.erl: -------------------------------------------------------------------------------- 1 | -module(return_fun_fail). 2 | 3 | -export([return_fun_union/0, 4 | return_fun_remote/0, 5 | return_fun_no_spec/0]). 6 | 7 | -spec return_fun_union() -> integer() | fun(() -> atom()). 8 | return_fun_union() -> 9 | fun nil/0. 10 | 11 | -spec return_fun_remote() -> fun((...) -> atom()). 12 | return_fun_remote() -> 13 | fun erlang:atom_to_list/1. 14 | 15 | -spec return_fun_no_spec() -> integer(). 16 | return_fun_no_spec() -> fun no_spec/0. 17 | 18 | -spec nil() -> []. 19 | nil() -> []. 20 | 21 | no_spec() -> ok. 22 | -------------------------------------------------------------------------------- /test/should_fail/send_fail.erl: -------------------------------------------------------------------------------- 1 | -module(send_fail). 2 | 3 | -export([foo/2, bar/2]). 4 | 5 | -spec foo(any(), ok) -> nok. 6 | foo(X, Y) -> X ! Y. 7 | 8 | -spec bar(any(), ok) -> nok. 9 | bar(X, Y) -> 10 | A = X ! Y, 11 | A. 12 | -------------------------------------------------------------------------------- /test/should_fail/shortcut_ops_fail.erl: -------------------------------------------------------------------------------- 1 | -module(shortcut_ops_fail). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec andalso_too_precise1(true, number()) -> number(). 6 | andalso_too_precise1(True, N) -> True andalso N. 7 | 8 | -spec andalso_too_precise2(false, number()) -> false. 9 | andalso_too_precise2(False, N) -> False andalso N. 10 | 11 | -spec orelse_too_precise1(false, number()) -> number(). 12 | orelse_too_precise1(False, N) -> False orelse N. 13 | 14 | -spec orelse_too_precise2(true, number()) -> true. 15 | orelse_too_precise2(True, N) -> True orelse N. 16 | 17 | -------------------------------------------------------------------------------- /test/should_fail/spec_and_fun_clause_intersection_fail.erl: -------------------------------------------------------------------------------- 1 | -module(spec_and_fun_clause_intersection_fail). 2 | 3 | -export([f/1, g/1, i/1]). 4 | 5 | -type t() :: {tag, integer()}. 6 | 7 | -spec f([t()]) -> string(); 8 | (t()) -> string(). 9 | f(asd) -> ""; 10 | f({tag, _}) -> "". 11 | 12 | -spec g([t()]) -> string(); 13 | (t()) -> string(). 14 | g([] = _Types) -> ""; 15 | g({tag, _}) -> "". 16 | 17 | -spec i([t()]) -> [string()]; 18 | (t()) -> string(). 19 | i([_|_] = Types) -> lists:map(fun i/1, Types); 20 | i({tag, _}) -> "". 21 | -------------------------------------------------------------------------------- /test/should_fail/string_literal.erl: -------------------------------------------------------------------------------- 1 | -module(string_literal). 2 | 3 | -export([f/0]). 4 | 5 | -spec f() -> ok. 6 | f() -> "". 7 | -------------------------------------------------------------------------------- /test/should_fail/tuple_union_arg_fail.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_arg_fail). 2 | 3 | -export([j/1]). 4 | 5 | -spec i(a, b) -> {a, b}; 6 | (d, e) -> {d, e}. 7 | i(V, U) -> {V, U}. 8 | 9 | %% This passes, though it shouldn't, because V is inferred to be d | a, 10 | %% and U is inferred to be b | e. 11 | %% If that was the case, then the call to i/2 could sometimes succeed. 12 | %% Not always, though! So it should already be considered an error. 13 | %% 14 | %% However, due to how the union is structured we know that when V=d, then U=b, 15 | %% and that combination is certain to fail. The same holds for V=a, U=e. 16 | -spec j({d, b} | {a, e}) -> {a, b} | {d, e}. 17 | j({V, U}) -> i(V, U). 18 | -------------------------------------------------------------------------------- /test/should_fail/tuple_union_fail.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_fail). 2 | 3 | -export([f/0]). 4 | -export([tuple_union/0]). 5 | 6 | -spec f() -> {integer()} | {boolean()}. 7 | f() -> 8 | {apa}. 9 | 10 | -spec tuple_union() -> {undefined, binary()} | {integer(), undefined}. 11 | tuple_union() -> 12 | {undefined, undefined}. 13 | -------------------------------------------------------------------------------- /test/should_fail/tuple_union_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_pattern). 2 | 3 | -export([tuple_union/1]). 4 | 5 | -spec tuple_union({undefined, {}} 6 | | {{}, undefined}) -> {}. 7 | tuple_union({undefined, undefined}) -> 8 | {}; 9 | tuple_union({{},{}}) -> 10 | {}. 11 | 12 | -------------------------------------------------------------------------------- /test/should_fail/tuple_union_refinement.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_refinement). 2 | -export([fail_1/1]). 3 | 4 | %% refine({a, b | c} | {b, c}, {b, c}) -> {a, b | c}. 5 | -spec fail_1({a, b | c} | {b, c}) -> {a, b}. 6 | fail_1({b, c}) -> {a, b}; 7 | fail_1(Tuple) -> Tuple. 8 | -------------------------------------------------------------------------------- /test/should_fail/type_refinement_fail.erl: -------------------------------------------------------------------------------- 1 | -module(type_refinement_fail). 2 | 3 | -export([imprecision_prevents_refinement/2, 4 | multi_pat_fail_1/2]). 5 | 6 | -spec imprecision_prevents_refinement(float(), a|b) -> b. 7 | imprecision_prevents_refinement(3.14, a) -> b; 8 | imprecision_prevents_refinement(_, X) -> X. 9 | 10 | -spec multi_pat_fail_1(a|b, a|b) -> {b, b}. 11 | multi_pat_fail_1(a, a) -> {b, b}; 12 | multi_pat_fail_1(A, B) -> {A, B}. %% Not only {b, b} here 13 | -------------------------------------------------------------------------------- /test/should_fail/unary_op.erl: -------------------------------------------------------------------------------- 1 | -module(unary_op). 2 | -export([fail/1, non_number_argument_to_minus/1]). 3 | 4 | -spec fail(number()) -> boolean(). 5 | fail(X) -> -X. 6 | 7 | -spec non_number_argument_to_minus(atom()) -> integer(). 8 | non_number_argument_to_minus(A) -> 9 | A = - A, 10 | A. 11 | -------------------------------------------------------------------------------- /test/should_fail/unary_plus_fail.erl: -------------------------------------------------------------------------------- 1 | -module(unary_plus_fail). 2 | -export([m/1, o/1, p/0]). 3 | 4 | -spec m(+1) -> {}. 5 | m(+2) -> 6 | {}. 7 | 8 | -spec o(boolean()) -> boolean(). 9 | o(X) -> 10 | +X. 11 | 12 | p() -> 13 | +m(+1). 14 | -------------------------------------------------------------------------------- /test/should_fail/union_with_any.erl: -------------------------------------------------------------------------------- 1 | -module(union_with_any). 2 | 3 | %% T | any() means 4 | %% "at least values of type T are possible; maybe also other values" 5 | 6 | -export([f1/1, f2/0, f3/1]). 7 | 8 | %% With spec 9 | -spec f1(atom() | any()) -> any(). 10 | f1(X) -> inc(X). 11 | 12 | %% Without spec 13 | f2() -> 14 | AtomOrAny = receive 15 | 1 -> get_atom(); 16 | Any -> Any 17 | end, 18 | inc(AtomOrAny). %% Fails because atom() is possible 19 | 20 | -spec get_atom() -> atom(). 21 | get_atom() -> banana. 22 | 23 | -spec inc(number()) -> number(). 24 | inc(N) -> N + 1. 25 | 26 | -record(r, {i :: integer()}). 27 | 28 | %% The variable is expected to have type #r{} but it has type undefined | any() 29 | -spec f3(any() | undefined) -> integer(). 30 | f3(R) -> 31 | R#r.i. 32 | -------------------------------------------------------------------------------- /test/should_fail/unreachable_after_refinement.erl: -------------------------------------------------------------------------------- 1 | -module(unreachable_after_refinement). 2 | 3 | -export([unreachable/1]). 4 | 5 | -spec unreachable(a|b) -> ok. 6 | unreachable(a) -> ok; 7 | unreachable(b) -> ok; 8 | unreachable(a) -> ok. %% <-- shouldn't happen.... 9 | -------------------------------------------------------------------------------- /test/should_pass/alias_in_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(alias_in_pattern). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec foo(tuple() | integer()) -> tuple(). 6 | foo(Left = {_X, _Y}) -> Left; 7 | foo(42 = N) -> {N+N}. 8 | 9 | -spec bar(tuple() | integer()) -> tuple(). 10 | bar({_,_,_} = Right) -> Right; 11 | bar(N = 33) -> {N+N}. 12 | 13 | -spec baz(tuple() | integer()) -> tuple(). 14 | baz({Inside, _} = {{}, 0}) -> Inside. 15 | 16 | -spec issue32({atom1, atom2} | atom4) -> {atom1, atom2} | atom3. 17 | issue32(Stuff) -> 18 | case Stuff of 19 | {atom1, _} = X -> X; 20 | _ -> atom3 21 | end. 22 | 23 | -------------------------------------------------------------------------------- /test/should_pass/andalso_any.erl: -------------------------------------------------------------------------------- 1 | -module(andalso_any). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f1() -> boolean(). 6 | f1() -> 7 | true andalso g1(). 8 | 9 | -spec g1() -> any(). 10 | g1() -> 3. 11 | 12 | -spec f2() -> boolean(). 13 | f2() -> 14 | true andalso g2(). 15 | 16 | g2() -> 5. 17 | 18 | f3() -> 19 | true andalso g3(). 20 | 21 | -spec g3() -> any(). 22 | g3() -> apa. 23 | -------------------------------------------------------------------------------- /test/should_pass/ann_types.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Test module for annotated types 2 | -module(ann_types). 3 | 4 | -export([f/2, g/2, h/1, annotated_named_record_arg/1, remote_named_record_arg/1]). 5 | 6 | -spec f(A :: atom(), integer()) -> {A :: term(), B :: number()}. 7 | f(A, B) -> 8 | {A, B}. 9 | 10 | -spec g(A :: atom(), Map :: map()) -> NewMap :: map(). 11 | g(A, Map) -> 12 | Map#{ A => any }. 13 | 14 | -type mytuple() :: tuple(). 15 | -spec h(Pat :: mytuple()) -> ok. 16 | h({}) -> 17 | ok. 18 | 19 | -record(r, {}). 20 | -type r() :: #r{}. 21 | -spec annotated_named_record_arg(R :: r()) -> {ok, R :: r()}. 22 | annotated_named_record_arg(#r{} = R) -> {ok, R}. 23 | 24 | -spec remote_named_record_arg(R :: user_types:my_empty_record()) -> 25 | {ok, R :: user_types:my_empty_record()}. 26 | remote_named_record_arg(#r{} = R) -> {ok, R}. 27 | 28 | -------------------------------------------------------------------------------- /test/should_pass/annotated_types.erl: -------------------------------------------------------------------------------- 1 | -module(annotated_types). 2 | 3 | -export([f/1, g/0, h/1, i/1]). 4 | 5 | -include_lib("../../include/gradualizer.hrl"). 6 | 7 | f(Expr) -> 8 | {call, _, _Name, Args} = Expr, 9 | Arity = ?assert_type(length(Args), arity()), 10 | do_stuff_with_arity(Arity). 11 | 12 | -spec g() -> non_neg_integer(). 13 | g() -> 14 | receive {age, Age} -> ?annotate_type(Age, non_neg_integer()) end. 15 | 16 | -spec h(non_neg_integer()) -> ok. 17 | h(N) -> 18 | do_stuff_with_arity(?assert_type(N, arity())). 19 | 20 | i(X) -> 21 | do_stuff_with_arity(?annotate_type(X, arity())). 22 | 23 | -spec do_stuff_with_arity(arity()) -> ok. 24 | do_stuff_with_arity(_Arity) -> ok. 25 | -------------------------------------------------------------------------------- /test/should_pass/any.erl: -------------------------------------------------------------------------------- 1 | -module(any). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec any(any()) -> any(). 6 | 7 | any(1) -> 8 | apa. 9 | -------------------------------------------------------------------------------- /test/should_pass/any_doesnt_have_type_none_pass.erl: -------------------------------------------------------------------------------- 1 | -module(any_doesnt_have_type_none_pass). 2 | 3 | -export([f/2]). 4 | 5 | -type type() :: abstract_type(). 6 | 7 | -type abstract_type() :: af_tuple_type(). 8 | 9 | -type anno() :: erl_anno:anno(). 10 | 11 | -type af_tuple_type() :: {'type', anno(), 'tuple', 'any'} 12 | | {'type', anno(), 'tuple', [abstract_type()]}. 13 | 14 | -spec f(type(), type()) -> ok. 15 | f(Ty1 = {type, _, tuple, Tys1}, Ty2 = {type, _, tuple, Tys2}) -> 16 | case {Tys1, Tys2} of 17 | {any, _} -> ok; 18 | _ -> ok 19 | end. 20 | -------------------------------------------------------------------------------- /test/should_pass/any_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(any_pattern). 2 | 3 | -export([pat_any/1]). 4 | 5 | %% This test case just increases coverage of add_any_types_pat 6 | %% with various patterns. 7 | -spec pat_any(any()) -> ok. 8 | pat_any({$a}) -> ok; 9 | pat_any({[]}) -> ok; 10 | pat_any({<<_>>}) -> ok; 11 | pat_any({_ = "string"}) -> ok; 12 | pat_any({"prefix" ++ _}) -> ok. 13 | -------------------------------------------------------------------------------- /test/should_pass/binary_exhaustiveness_checking.erl: -------------------------------------------------------------------------------- 1 | -module(binary_exhaustiveness_checking). 2 | 3 | -export([f/1, g/1, h/1, k/1, l/1]). 4 | 5 | f(<<>>) -> ok; 6 | f(<<_:8, _/binary>>) -> ok. 7 | 8 | -spec g(binary()) -> ok. 9 | g(<<_:8, _/binary>>) -> ok; 10 | g(<<>>) -> ok. 11 | 12 | -spec h(binary()) -> ok. 13 | h(<<_:8, _/binary>>) -> ok; 14 | h(<<>>) -> ok. 15 | 16 | -spec k(binary()) -> ok. 17 | k(<<_:4, _:4, _/binary>>) -> ok. 18 | 19 | %% This case should pass, since we should detect that <> is a complex pattern for 20 | %% which we cannot guarantee it exhausts the binary() type (see `is_bin_pat_exhaustive') 21 | %% and therefore not try to do exhaustiveness checking. 22 | -spec l(binary()) -> ok. 23 | l(B) -> 24 | V = $A, 25 | case B of 26 | <<>> -> 27 | ok; 28 | <> -> 29 | ok 30 | end. 31 | -------------------------------------------------------------------------------- /test/should_pass/binary_in_union.erl: -------------------------------------------------------------------------------- 1 | -module(binary_in_union). 2 | -export([ 3 | iodata_binary/0, 4 | nested_bitstrings/0 5 | ]). 6 | 7 | -spec iodata_binary() -> iodata(). 8 | iodata_binary() -> 9 | <<<<"A">> || _ <- lists:seq(1, 10)>>. 10 | 11 | -type nested() :: string() | bitstring() | binary(). 12 | -spec nested_bitstrings() -> nested() | boolean() | bitstring(). 13 | nested_bitstrings() -> 14 | << <<1:N>> || N <- lists:seq(1, 12)>>. 15 | -------------------------------------------------------------------------------- /test/should_pass/binary_literal_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(binary_literal_pattern). 2 | 3 | -export([f/1]). 4 | 5 | -type t1() :: binary(). 6 | -type option(T) :: undefined | T. 7 | 8 | -spec f(option(t1())) -> ok | error | other. 9 | f(T) -> 10 | case T of 11 | undefined -> error; 12 | %% The next line causes: 13 | %% The pattern <<"ok">> on line 14 doesn't have the type undefined | <<_:_*8>> 14 | <<"ok">> -> ok; 15 | _ -> other 16 | end. 17 | -------------------------------------------------------------------------------- /test/should_pass/bitstring.erl: -------------------------------------------------------------------------------- 1 | -module(bitstring). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec bin1(binary()) -> any(). 6 | bin1(<>) -> 7 | A+B. 8 | 9 | -spec bin2(float()) -> binary(). 10 | bin2(A) -> 11 | <>. 12 | 13 | bin3(A) -> 14 | <>. 15 | 16 | bin4(A,B) -> 17 | <>. 18 | 19 | bin5() -> 20 | <<"abc", 42, "abc"/utf32, "abc"/float, 42/float-little, 21 | (<<"abc">>):8/bits, (<<"abc">>)/bytes>>. 22 | 23 | -spec bin6(any(), any()) -> <<_:_*6>>. 24 | bin6(A, B) -> 25 | <<0:A/integer-unit:36, 1:B/integer-unit:30>>. 26 | -------------------------------------------------------------------------------- /test/should_pass/block_scope.erl: -------------------------------------------------------------------------------- 1 | -module(block_scope). 2 | 3 | -export([foo/0]). 4 | 5 | foo() -> 6 | begin 7 | X = 1, 8 | Y = 2 9 | end, 10 | X,Y. 11 | -------------------------------------------------------------------------------- /test/should_pass/bool.erl: -------------------------------------------------------------------------------- 1 | -module(bool). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec b(boolean(), boolean()) -> boolean(). 6 | b(B1, B2) -> 7 | B1 andalso B2. 8 | 9 | %% variable bindings should propagate from first to second arg of orelse 10 | -spec b2() -> boolean(). 11 | b2() -> 12 | begin 13 | A = f(), 14 | is_integer(A) 15 | end 16 | orelse 17 | is_float(A). 18 | 19 | -spec f() -> number(). 20 | f() -> 1. 21 | -------------------------------------------------------------------------------- /test/should_pass/bounded_funs.erl: -------------------------------------------------------------------------------- 1 | -module(bounded_funs). 2 | 3 | -export([calls/0, funs/0]). 4 | 5 | -spec calls() -> gradualizer:top(). 6 | calls() -> 7 | V1 = g(myatom), 8 | V2 = ets:lookup_element(myatom, asd, 2), 9 | V3 = prepend(myatom, [asd]), 10 | {V1, V2, V3}. 11 | 12 | funs() -> 13 | F1 = fun g/1, 14 | F2 = fun ets:lookup_element/3, 15 | F3 = fun prepend/2, 16 | {F1, F2, F3}. 17 | 18 | -spec g(Atom) -> any() when Atom :: atom(). 19 | g(A) -> 20 | A. 21 | 22 | %% Constraint with free var E 23 | -spec prepend(Elem, [E]) -> [E] when Elem :: E. 24 | prepend(Elem, L) -> 25 | [Elem|L]. 26 | -------------------------------------------------------------------------------- /test/should_pass/call_intersection_function_with_union_arg_pass.erl: -------------------------------------------------------------------------------- 1 | -module(call_intersection_function_with_union_arg_pass). 2 | 3 | -export([f1/1, 4 | f2/1, 5 | g1/1, 6 | g2/1, 7 | i1/2, 8 | k1/2, 9 | k2/2]). 10 | 11 | -spec f1(a) -> a. 12 | f1(V) -> 13 | h(V). 14 | 15 | -spec f2(a | b) -> a | b. 16 | f2(V) -> 17 | h(V). 18 | 19 | -spec g1(a) -> a. 20 | g1(V) -> 21 | A = h(V), 22 | A. 23 | 24 | -spec g2(a | b) -> a | b. 25 | g2(V) -> 26 | A = h(V), 27 | A. 28 | 29 | -spec h(a) -> a; 30 | (b) -> b. 31 | h(V) -> V. 32 | 33 | -type t() :: t1 | t2. 34 | -type u() :: u1 | u2. 35 | 36 | -spec i1(t2, u2) -> two. 37 | i1(T, U) -> 38 | j(T, U). 39 | 40 | -spec j(t1, u1) -> one; 41 | (t2, u2) -> two. 42 | j(t1, u1) -> one; 43 | j(t2, u2) -> two. 44 | 45 | -spec k1(_, _) -> any(). 46 | k1(T, U) -> 47 | j(T, U). 48 | 49 | %% intentionally no spec 50 | k2(T, U) -> 51 | j(T, U). 52 | -------------------------------------------------------------------------------- /test/should_pass/case.erl: -------------------------------------------------------------------------------- 1 | -module('case'). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | cse(X) -> 6 | case X of 7 | true -> 8 | false; 9 | false -> 10 | true 11 | end. 12 | -------------------------------------------------------------------------------- /test/should_pass/case_of_record_with_user_defined.erl: -------------------------------------------------------------------------------- 1 | -module(case_of_record_with_user_defined). 2 | 3 | -export([foo/2]). 4 | -type pred() :: fun((term()) -> boolean()). 5 | 6 | -record(state, { pred :: pred()}). 7 | 8 | -spec foo(term(), #state{}) -> boolean(). 9 | foo(Event, State) -> 10 | #state{ pred = Pred} = State, 11 | case Pred(Event) of 12 | true -> true; 13 | _ -> false 14 | end. 15 | -------------------------------------------------------------------------------- /test/should_pass/catch_expr_pass.erl: -------------------------------------------------------------------------------- 1 | -module(catch_expr_pass). 2 | 3 | -export([foo/1]). 4 | 5 | -spec foo(ok) -> ok. 6 | foo(X) -> 7 | A = (catch X), 8 | A. 9 | -------------------------------------------------------------------------------- /test/should_pass/covariant_map_keys_pass.erl: -------------------------------------------------------------------------------- 1 | -module(covariant_map_keys_pass). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec foo(#{ a | b | c := d }) -> ok. 6 | foo(_) -> ok. 7 | 8 | -spec bar(#{ a | c := d }) -> ok. 9 | bar(X) -> foo(X). 10 | 11 | -------------------------------------------------------------------------------- /test/should_pass/cyclic_otp_specs.erl: -------------------------------------------------------------------------------- 1 | -module(cyclic_otp_specs). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% We shouldn't fail on cyclic dependencies in OTP specs. 6 | %% Either make them work, or override the specs. 7 | 8 | -spec flatten([term()]) -> [term()]. 9 | flatten(Xs) -> lists:flatten(Xs). 10 | -------------------------------------------------------------------------------- /test/should_pass/erlang_error_args_none_pass.erl: -------------------------------------------------------------------------------- 1 | -module(erlang_error_args_none_pass). 2 | 3 | -export([f/0, 4 | g/0]). 5 | 6 | -spec f() -> atom(). 7 | f() -> 8 | erlang:error(42, none), 9 | ok. 10 | 11 | -spec g() -> atom(). 12 | g() -> 13 | erlang:error(42, none, []), 14 | ok. 15 | -------------------------------------------------------------------------------- /test/should_pass/exhaustiveness_union_types.erl: -------------------------------------------------------------------------------- 1 | -module(exhaustiveness_union_types). 2 | 3 | -export([g/0]). 4 | 5 | -spec f() -> ok | {error, foo} | {error, bar}. 6 | f() -> ok. 7 | 8 | -spec g() -> integer(). 9 | g() -> 10 | case f() of 11 | ok -> 42; 12 | {error, _Sth} -> 43 13 | end. -------------------------------------------------------------------------------- /test/should_pass/factorial.erl: -------------------------------------------------------------------------------- 1 | -module(factorial). 2 | 3 | -export([factorial/1]). 4 | 5 | %% This tests multiple things: 6 | %% * Type refinement of the argument. After the first clase, 7 | %% we have N :: pos_integer(). 8 | %% * Multiplication is closed under pos_integer() 9 | %% * pos_integer() - 1 :: non_neg_integer() 10 | -spec factorial(non_neg_integer()) -> pos_integer(). 11 | factorial(0) -> 1; 12 | factorial(N) -> N * factorial(N - 1). 13 | -------------------------------------------------------------------------------- /test/should_pass/float.erl: -------------------------------------------------------------------------------- 1 | -module(float). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f() -> float(). 6 | f() -> 7 | 2.0. 8 | 9 | -spec g() -> float() | banana. 10 | g() -> 11 | 3.14. 12 | 13 | issue68_a(1.0) -> banana; 14 | issue68_a(_) -> apple. 15 | 16 | -spec issue68_b(_) -> apple | banana. 17 | issue68_b(1.0) -> banana; 18 | issue68_b(_) -> apple. 19 | -------------------------------------------------------------------------------- /test/should_pass/flow.erl: -------------------------------------------------------------------------------- 1 | -module(flow). 2 | 3 | -export([foo/0, bar/1]). 4 | 5 | foo() -> 6 | bar(apa). 7 | 8 | -spec bar(apa | bepa) -> true | false. 9 | bar(apa) -> 10 | true; 11 | bar(bepa) -> 12 | false. 13 | -------------------------------------------------------------------------------- /test/should_pass/fun_capture.erl: -------------------------------------------------------------------------------- 1 | -module(fun_capture). 2 | 3 | -compile([nowarn_unused_vars, nowarn_shadow_vars]). 4 | -compile([export_all, nowarn_export_all]). 5 | 6 | -spec f(integer()) -> fun(([atom()]) -> [atom()]). 7 | f(X) -> 8 | (fun (X) -> 9 | X ++ [] 10 | end). 11 | -------------------------------------------------------------------------------- /test/should_pass/fun_spec.erl: -------------------------------------------------------------------------------- 1 | -module(fun_spec). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f() -> fun((any()) -> any()). 6 | f() -> fun (X) -> X end. 7 | -------------------------------------------------------------------------------- /test/should_pass/guard_sequences_pass.erl: -------------------------------------------------------------------------------- 1 | -module(guard_sequences_pass). 2 | 3 | -export([foo/2]). 4 | 5 | -spec foo(List1, List2) -> List2 when 6 | List1 :: [Int], 7 | List2 :: [Int|{Int,Int}], 8 | Int :: integer(). 9 | foo([], Acc) -> 10 | Acc; 11 | foo([H | T], []) -> 12 | foo(T, [H]); 13 | foo([H | T], [HA | TA]) when is_integer(HA), H == HA + 1 -> 14 | foo(T, [{HA, H} | TA]); 15 | foo([H | T], [{HAB, HAE} | TA]) when H == HAE + 1 -> 16 | foo(T, [{HAB, H} | TA]); 17 | foo([H | T], Acc) -> 18 | foo(T, [H | Acc]). 19 | -------------------------------------------------------------------------------- /test/should_pass/if_expr.erl: -------------------------------------------------------------------------------- 1 | -module(if_expr). 2 | 3 | -export([foo/1]). 4 | 5 | -spec foo(integer()) -> atom(). 6 | foo(X) -> 7 | A = if X < 0 -> neg; 8 | X =:= 0 -> zero; 9 | X > 0 -> pos 10 | end, 11 | A. 12 | -------------------------------------------------------------------------------- /test/should_pass/imported.erl: -------------------------------------------------------------------------------- 1 | -module(imported). 2 | 3 | -export([foo/1]). 4 | -import(any, [any/1]). 5 | 6 | -spec foo(integer()) -> atom(). 7 | foo(X) -> any(X). 8 | -------------------------------------------------------------------------------- /test/should_pass/int.erl: -------------------------------------------------------------------------------- 1 | -module(int). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f(integer()) -> integer(). 6 | f(N) -> 7 | N. 8 | 9 | %% test singleton char, char ranges, and the merging of them 10 | -spec char_range() -> $a..$d | $b..$y | $z. 11 | char_range() -> 12 | $c. 13 | -------------------------------------------------------------------------------- /test/should_pass/intersection_pass.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_pass). 2 | 3 | -export([f/1, g/0, h/0, num/1]). 4 | 5 | -spec f(integer()) -> integer(); 6 | (boolean()) -> boolean(). 7 | f(X) -> 8 | X. 9 | 10 | g() -> 11 | f(true). 12 | 13 | -spec h() -> boolean(). 14 | h() -> 15 | f(false). 16 | 17 | -spec num(1) -> one; 18 | (2) -> two. 19 | num(1) -> one; 20 | num(2) -> two. 21 | -------------------------------------------------------------------------------- /test/should_pass/intersection_with_any_pass.erl: -------------------------------------------------------------------------------- 1 | -module(intersection_with_any_pass). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec any_refined_using_guard(any()) -> 5. 6 | any_refined_using_guard(X) when is_integer(X) -> X. 7 | 8 | -spec intersection_using_constraints(X) -> X when 9 | X :: integer(), 10 | X :: any(). 11 | intersection_using_constraints(X) -> X. 12 | 13 | -spec guess_two_dice(integer(), integer()) -> 0..6. 14 | guess_two_dice(X, Y) -> 15 | case roll_two_dice() of 16 | {X, Y} -> 17 | X; 18 | _GuessFail -> 19 | 0 20 | end. 21 | 22 | %% untyped helper returning {1..6, 1..6} 23 | roll_two_dice() -> 24 | {rand:uniform(6), rand:uniform(6)}. 25 | -------------------------------------------------------------------------------- /test/should_pass/iodata.erl: -------------------------------------------------------------------------------- 1 | -module(iodata). 2 | 3 | -export([f/0, g/0]). 4 | 5 | -spec f() -> iolist(). 6 | f() -> 7 | [$a|list_to_binary("b")]. 8 | 9 | -spec g() -> term(). 10 | g() -> 11 | expect_iodata("foo"), 12 | expect_iodata(["foo"]), 13 | expect_iodata(<<"foo">>). 14 | 15 | -spec expect_iodata(iodata()) -> any(). 16 | expect_iodata(_IOData) -> 17 | ok. 18 | -------------------------------------------------------------------------------- /test/should_pass/issue131.erl: -------------------------------------------------------------------------------- 1 | -module(issue131). 2 | -export([f/0]). 3 | -spec f() -> #{ a := b } | undefined. 4 | f() -> #{ a => b }. 5 | -------------------------------------------------------------------------------- /test/should_pass/lc.erl: -------------------------------------------------------------------------------- 1 | -module(lc). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | bin_gen_1() -> 6 | [X || <> <= bin_fixed_size()]. 7 | 8 | -spec bin_gen_2(<<_:16>>) -> [integer()]. 9 | bin_gen_2(Bin) -> 10 | [X || <> <= Bin]. 11 | 12 | -spec any_list() -> list(). 13 | any_list() -> 14 | [X || X <- [1, 2, 3]]. 15 | 16 | -spec union_of_lists() -> [integer()] | [atom()]. 17 | union_of_lists() -> 18 | [X || X <- [apa, bepa]]. 19 | 20 | -spec bin_fixed_size() -> <<_:16>>. 21 | bin_fixed_size() -> <<"xy">>. 22 | -------------------------------------------------------------------------------- /test/should_pass/lc_generator_not_none.erl: -------------------------------------------------------------------------------- 1 | -module(lc_generator_not_none). 2 | 3 | %% Compare with test/should_fail/lc_generator_not_none_fail.erl 4 | 5 | -export([g/1, 6 | h/1]). 7 | 8 | -type t() :: {a, d} 9 | | {a, [t()]}. 10 | 11 | -spec g(t()) -> [t()]. 12 | g({a, d} = T) -> 13 | [T]; 14 | g({a, Ts}) -> 15 | [ T || T <- Ts ]. 16 | 17 | -spec h(t()) -> [t()]. 18 | h({a, Ts}) -> 19 | [ T || T <- Ts ]. 20 | -------------------------------------------------------------------------------- /test/should_pass/lc_var_binds_in_filters.erl: -------------------------------------------------------------------------------- 1 | -module(lc_var_binds_in_filters). 2 | 3 | -export([f/0]). 4 | 5 | -spec f() -> [ok]. 6 | f() -> 7 | [ ok 8 | || begin 9 | Id = 1, 10 | true 11 | end, 12 | Id * 2 > 0 ]. 13 | -------------------------------------------------------------------------------- /test/should_pass/list.erl: -------------------------------------------------------------------------------- 1 | -module(list). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f([integer()]) -> integer(). 6 | 7 | f([]) -> 8 | 0; 9 | f([A|As]) -> 10 | A + f(As). 11 | 12 | string_tail() -> 13 | [ 1 | atom_to_list(foo)]. 14 | 15 | -spec list_union_tail([atom()] | [integer()]) -> any(). 16 | list_union_tail(L) -> 17 | [ 1 | L ]. 18 | 19 | list_any_tail() -> 20 | [ 1 | return_list() ]. 21 | 22 | -spec return_list() -> nonempty_list(). 23 | return_list() -> 24 | [2, 3]. 25 | -------------------------------------------------------------------------------- /test/should_pass/list_concat_op_pass.erl: -------------------------------------------------------------------------------- 1 | -module(list_concat_op_pass). 2 | 3 | -export([nil_concat_fun_elem_gives_elem/0, 4 | nonempty1_concat_fun_elem_gives_improper/0, 5 | nonempty2_concat_fun_elem_gives_improper/0, 6 | nil_concat_fun_nonempty_gives_proper/0, 7 | nil_concat_op_nonempty_gives_proper/0, 8 | nonempty_concat_fun_nonempty_gives_proper/0, 9 | nonempty_concat_op_nonempty_gives_proper/0, 10 | proper_concat_fun_proper_gives_proper/0]). 11 | 12 | -spec nil_concat_fun_elem_gives_elem() -> b. 13 | nil_concat_fun_elem_gives_elem() -> 14 | erlang:'++'([], b). 15 | 16 | -spec nonempty1_concat_fun_elem_gives_improper() -> nonempty_improper_list(a, b). 17 | nonempty1_concat_fun_elem_gives_improper() -> 18 | erlang:'++'([a], b). 19 | 20 | -spec nonempty2_concat_fun_elem_gives_improper() -> nonempty_improper_list(atom(), c). 21 | nonempty2_concat_fun_elem_gives_improper() -> 22 | erlang:'++'([a,b], c). 23 | 24 | -spec nil_concat_fun_nonempty_gives_proper() -> [atom()]. 25 | nil_concat_fun_nonempty_gives_proper() -> 26 | erlang:'++'([], [a]). 27 | 28 | -spec nil_concat_op_nonempty_gives_proper() -> [atom()]. 29 | nil_concat_op_nonempty_gives_proper() -> 30 | [] ++ [a]. 31 | 32 | -spec nonempty_concat_fun_nonempty_gives_proper() -> [atom()]. 33 | nonempty_concat_fun_nonempty_gives_proper() -> 34 | erlang:'++'([a,b], [c]). 35 | 36 | -spec nonempty_concat_op_nonempty_gives_proper() -> [atom()]. 37 | nonempty_concat_op_nonempty_gives_proper() -> 38 | [a,b] ++ [c]. 39 | 40 | -spec proper_concat_fun_proper_gives_proper() -> [integer()]. 41 | proper_concat_fun_proper_gives_proper() -> 42 | erlang:'++'(generate_list(), generate_list()). 43 | 44 | -spec generate_list() -> [integer()]. 45 | generate_list() -> lists:seq(1, 2). 46 | -------------------------------------------------------------------------------- /test/should_pass/list_exhaustiveness_checking_regressions.erl: -------------------------------------------------------------------------------- 1 | -module(list_exhaustiveness_checking_regressions). 2 | 3 | -export([f/2, g/1, h/1, k/1]). 4 | 5 | f( Needle, [Needle | _]) -> ok; 6 | f( Needle, [_ | Haystack]) -> f(Needle, Haystack); 7 | f(_Needle, []) -> ok. 8 | 9 | -spec g([a | b]) -> ok. 10 | g([a | _Haystack]) -> ok; 11 | g([_ | Haystack]) -> g(Haystack); 12 | g([]) -> ok. 13 | 14 | -spec h([a | b]) -> ok. 15 | h([a | _Haystack]) -> ok; 16 | h([]) -> ok; 17 | h([_ | Haystack]) -> h(Haystack). 18 | 19 | -spec k(list(atom())) -> integer(). 20 | k([_, _ | _]) -> 1; 21 | k(_) -> 2. 22 | -------------------------------------------------------------------------------- /test/should_pass/list_exhaustiveness_checking_regressions2.erl: -------------------------------------------------------------------------------- 1 | -module(list_exhaustiveness_checking_regressions2). 2 | 3 | -export([i/1, j/1]). 4 | 5 | -spec i([atom()]) -> ok. 6 | i([]) -> ok; 7 | i([Cs]) -> ok; 8 | i([C1, C2 | Cs]) -> ok. 9 | 10 | -spec j([atom()]) -> integer(). 11 | j([]) -> 1; 12 | j([C1, C2 | _]) -> 2; 13 | j([Cs]) -> 3. 14 | -------------------------------------------------------------------------------- /test/should_pass/list_exhaustiveness_checking_unreachable_clause_regression.erl: -------------------------------------------------------------------------------- 1 | -module(list_exhaustiveness_checking_unreachable_clause_regression). 2 | 3 | -export([f/2]). 4 | 5 | f( Needle, [Needle | _]) -> ok; 6 | f( Needle, [_ | Haystack]) -> f(Needle, Haystack); 7 | f(_Needle, []) -> ok. 8 | -------------------------------------------------------------------------------- /test/should_pass/list_infer_pass.erl: -------------------------------------------------------------------------------- 1 | -module(list_infer_pass). 2 | 3 | -export([f/0]). 4 | 5 | f() -> 6 | V = [1, 2], 7 | sum(V). 8 | 9 | -spec sum([integer()]) -> any(). 10 | sum([Int | Rest ]) -> Int + sum(Rest); 11 | sum([]) -> 0. 12 | -------------------------------------------------------------------------------- /test/should_pass/list_op_pass.erl: -------------------------------------------------------------------------------- 1 | -module(list_op_pass). 2 | 3 | -export([append/0, 4 | subtract/0]). 5 | 6 | %% Checking this function used to crash. 7 | %% Now that we approximate type vars for list ops with any, it passes. 8 | append() -> 9 | lists:foldl(fun(X, A) -> A ++ [X] end, [], []). 10 | 11 | %% Checking this function used to crash. 12 | %% Now that we approximate type vars for list ops with any, it passes. 13 | subtract() -> 14 | lists:foldl(fun(X, A) -> A -- [X] end, [], []). 15 | -------------------------------------------------------------------------------- /test/should_pass/map.erl: -------------------------------------------------------------------------------- 1 | -module(map). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec unknown_map1(#{a => b}) -> map(). 6 | unknown_map1(M) -> M. 7 | 8 | -spec unknown_map2(map()) -> #{a => b}. 9 | unknown_map2(M) -> M. 10 | 11 | -spec mandatory_ok(#{a := b}) -> #{a => b}. 12 | mandatory_ok(M) -> M. 13 | 14 | -spec union_value(#{a => b} | #{a => c}) -> #{a => b | c}. 15 | union_value(M) -> M. 16 | -------------------------------------------------------------------------------- /test/should_pass/map_as_argument_update.erl: -------------------------------------------------------------------------------- 1 | -module(map_as_argument_update). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% See https://github.com/josefs/Gradualizer/pull/233 for the bug report, 6 | %% and https://github.com/josefs/Gradualizer/pull/237 for the fix. 7 | 8 | -spec t(list()) -> map(). 9 | t(Rows) -> 10 | lists:foldl(fun(Id, Map) -> 11 | Map#{id := Id} 12 | end, #{id => undefined}, Rows). 13 | -------------------------------------------------------------------------------- /test/should_pass/map_creation.erl: -------------------------------------------------------------------------------- 1 | -module(map_creation). 2 | -export([f/0, g/0, h/0]). 3 | 4 | -spec f() -> #{apa := bepa}. 5 | f() -> #{apa => bepa}. 6 | 7 | -type typed_map() :: #{field_a := binary()}. 8 | 9 | -spec g() -> typed_map(). 10 | g() -> 11 | #{field_a => <<"ala ma kota">>}. 12 | 13 | -spec h() -> #{inner := #{a := 5}}. 14 | h() -> #{inner => #{a => 5}}. 15 | -------------------------------------------------------------------------------- /test/should_pass/map_field_valid_update.erl: -------------------------------------------------------------------------------- 1 | -module(map_field_valid_update). 2 | 3 | -export([f/1, g/1, h/1, i/1, j/1]). 4 | 5 | -spec f(#{a := boolean()}) -> #{a := integer()}. 6 | f(#{} = Ctx) -> 7 | Ctx#{a := 5}. 8 | 9 | %% Ctx might not have a field `a`, but Ctx2 has for sure (mandatory) 10 | -spec g(map()) -> #{a := any(), _ => _}. 11 | g(Ctx) -> 12 | Ctx2 = Ctx#{a => 5}, 13 | Ctx2. 14 | 15 | %% When updating map() it can still have any other optional key 16 | -spec h(map()) -> #{a := any(), b => any()}. 17 | h(Ctx) -> 18 | Ctx2 = Ctx#{a => 5}, 19 | Ctx2. 20 | 21 | -spec i(#{a := any(), b => any(), c => any()}) 22 | -> #{a := any(), b => any(), c := any(), d := any()}. 23 | i(Ctx) -> 24 | Ctx2 = Ctx#{c => 1, d => 4}, 25 | Ctx2. 26 | 27 | -spec j(#{}) -> #{a := any(), b => any()}. 28 | j(Ctx) -> 29 | Ctx2 = Ctx#{a => 5}, 30 | Ctx2. 31 | -------------------------------------------------------------------------------- /test/should_pass/map_infer_pass.erl: -------------------------------------------------------------------------------- 1 | -module(map_infer_pass). 2 | 3 | -export([not_good/1, 4 | kaboom1/0, 5 | kaboom2/0]). 6 | 7 | -spec not_good(#{good | bad := integer()}) -> integer(). 8 | not_good(#{good := _}) -> 0; 9 | not_good(#{bad := _}) -> 1. 10 | 11 | -spec kaboom1() -> integer(). 12 | kaboom1() -> 13 | M = #{bad => 0}, 14 | not_good(M). 15 | 16 | -spec kaboom2() -> integer(). 17 | kaboom2() -> 18 | not_good(#{bad => 0}). 19 | -------------------------------------------------------------------------------- /test/should_pass/map_passing_expr.erl: -------------------------------------------------------------------------------- 1 | -module(map_passing_expr). 2 | 3 | -export([foo/0]). 4 | 5 | -spec foo() -> #{atom() => integer()}. 6 | foo() -> 7 | A = #{ok => 42}, 8 | A. 9 | -------------------------------------------------------------------------------- /test/should_pass/map_passing_subtyping.erl: -------------------------------------------------------------------------------- 1 | -module(map_passing_subtyping). 2 | 3 | -export([m1/0, m2/0]). 4 | 5 | -spec m1() -> #{foo := atom(), bar => optional}. 6 | m1() -> 7 | #{foo => bar}. 8 | 9 | -spec m2() -> #{foo => atom()}. 10 | m2() -> 11 | #{foo => bar}. 12 | -------------------------------------------------------------------------------- /test/should_pass/map_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(map_pattern). 2 | 3 | -export([f/1, 4 | key_subtype/1, 5 | map_union/1, 6 | any_map/1, 7 | map_term/1, 8 | map_pattern_no_spec/1]). 9 | 10 | -type t() :: #{apa := integer(), bepa := boolean()}. 11 | 12 | -spec f(t()) -> boolean(). 13 | f(#{bepa := Bepa}) -> 14 | Bepa. 15 | 16 | -spec key_subtype(#{atom() => integer()}) -> integer(). 17 | key_subtype(#{banana := N}) -> 18 | N. 19 | 20 | -spec map_union(#{a => atom()} | #{b := boolean()}) -> atom(). 21 | map_union(#{a := A}) -> A; 22 | map_union(#{b := B}) -> B. 23 | 24 | -spec any_map(map()) -> ok. 25 | any_map(#{apa := _}) -> ok. 26 | 27 | -spec map_term(gradualizer:top()) -> any(). 28 | map_term(#{}) -> 29 | ok. 30 | 31 | map_pattern_no_spec(#{} = _Map) -> ok. 32 | -------------------------------------------------------------------------------- /test/should_pass/map_update.erl: -------------------------------------------------------------------------------- 1 | -module(map_update). 2 | 3 | -export([mapup/2, 4 | mapup2/2]). 5 | 6 | -spec mapup(boolean(), map()) -> map(). 7 | mapup(A, M1) -> 8 | M2 = case A of 9 | true -> 10 | M1#{a := 1}; 11 | false -> 12 | M1#{b := 1} 13 | end, 14 | M2#{c := 1}. 15 | 16 | %% `#{_ => _}', `#{any() => any()}', and `map()' are all equivalent. 17 | -spec mapup2(boolean(), #{_ => _}) -> map(). 18 | mapup2(A, M1) -> 19 | {M2} = case A of 20 | true -> 21 | {M1#{a := 1}}; 22 | false -> 23 | {M1#{b := 1}} 24 | end, 25 | M2#{c := 1}. 26 | -------------------------------------------------------------------------------- /test/should_pass/map_update_with_record_field.erl: -------------------------------------------------------------------------------- 1 | -module(map_update_with_record_field). 2 | 3 | -export([mapup3/2]). 4 | 5 | %% When record `r' is defined or included and used directly in the map type definition, 6 | %% then `mapup3' passes typechecking. 7 | %-record(r, {}). 8 | %-type m() :: #{rec := #r{}}. 9 | 10 | %% However, when the actual remote type and corresponding remote record is used, 11 | %% then `mapup3' fails to typecheck, although it still should. 12 | -type m() :: #{rec := user_types:my_empty_record()}. 13 | 14 | -spec mapup3(m(), user_types:my_empty_record()) -> m(). 15 | mapup3(TypedMap, R) -> 16 | TypedMap#{rec => R}. 17 | -------------------------------------------------------------------------------- /test/should_pass/maybe_expr_pass.erl: -------------------------------------------------------------------------------- 1 | -module(maybe_expr_pass). 2 | 3 | -ifdef(OTP_RELEASE). 4 | -if(?OTP_RELEASE >= 25). 5 | -if(?FEATURE_AVAILABLE(maybe_expr)). 6 | 7 | -feature(maybe_expr, enable). 8 | 9 | -export([check1/0, check2/0]). 10 | -export([infer1/0, infer2/1]). 11 | 12 | -spec check1() -> integer(). 13 | check1() -> 14 | maybe 15 | ok ?= ok, 16 | 1 17 | end. 18 | 19 | -spec check2() -> integer(). 20 | check2() -> 21 | maybe 22 | ok ?= not_ok, 23 | 1 24 | else 25 | _ -> 2 26 | end. 27 | 28 | -spec infer1() -> integer(). 29 | infer1() -> 30 | R = maybe 31 | ok ?= ok, 32 | 1 33 | end, 34 | R. 35 | 36 | -spec infer2(string()) -> {ok, string()} | error. 37 | infer2(Val) -> 38 | R = maybe 39 | "ok" ?= Val, 40 | {ok, Val} 41 | else 42 | _ -> error 43 | end, 44 | R. 45 | 46 | -endif. %% FEATURE_AVAILABLE 47 | -endif. %% OTP >= 25 48 | -endif. %% OTP_RELEASE 49 | -------------------------------------------------------------------------------- /test/should_pass/messaging_pass.erl: -------------------------------------------------------------------------------- 1 | -module(messaging_pass). 2 | 3 | -export([main/0, server/0, server1/0, server2/0, server3/0, server4/0]). 4 | 5 | -spec ok() -> ok. 6 | ok() -> ok. 7 | 8 | server() -> 9 | receive 10 | quit -> 11 | io:format("Quitting!~n"); 12 | Foo -> 13 | io:format("~p~n", [Foo]), 14 | server() 15 | end. 16 | 17 | -spec server1() -> ok. 18 | server1() -> 19 | receive 20 | quit -> 21 | ok(); 22 | Foo -> 23 | io:format("~p~n", [Foo]), 24 | server1() 25 | end. 26 | 27 | -spec server2() -> ok. 28 | server2() -> 29 | A = receive 30 | quit -> 31 | ok(); 32 | Foo -> 33 | io:format("~p~n", [Foo]), 34 | server2() 35 | end, 36 | A. 37 | 38 | -spec server3() -> ok. 39 | server3() -> 40 | receive 41 | quit -> 42 | ok(); 43 | Foo -> 44 | io:format("~p~n", [Foo]), 45 | server3() 46 | after 1000 -> ok 47 | end. 48 | 49 | -spec server4() -> ok. 50 | server4() -> 51 | A = receive 52 | quit -> 53 | ok(); 54 | Foo -> 55 | io:format("~p~n", [Foo]), 56 | server4() 57 | after 1000 -> ok() 58 | end, 59 | A. 60 | 61 | main() -> 62 | Pid = spawn(fun server/0), 63 | Pid!{apa,bepa}, 64 | Pid!quit. 65 | -------------------------------------------------------------------------------- /test/should_pass/minus.erl: -------------------------------------------------------------------------------- 1 | -module(minus). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec m(-1) -> {}. 6 | m(-1) -> 7 | {}. 8 | -------------------------------------------------------------------------------- /test/should_pass/module_info.erl: -------------------------------------------------------------------------------- 1 | -module(module_info). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec nullary_direct() -> [{atom(), atom() | list() | binary()}]. 6 | nullary_direct() -> 7 | erlang:module_info(). 8 | 9 | -spec nullary_var() -> [{atom(), any()}]. 10 | nullary_var() -> 11 | I = erlang:module_info(), 12 | I. 13 | 14 | -spec unary_direct() -> binary(). 15 | unary_direct() -> 16 | erlang:module_info(md5). 17 | 18 | -spec unary_var() -> atom(). 19 | unary_var() -> 20 | I = erlang:module_info(module), 21 | I. -------------------------------------------------------------------------------- /test/should_pass/module_info_higher_arity.erl: -------------------------------------------------------------------------------- 1 | -module(module_info_higher_arity). 2 | 3 | -export([two_plus_four/0, module_info/2]). 4 | 5 | -spec module_info(number(), number()) -> number(). 6 | module_info(A, B) -> A + B. 7 | 8 | -spec two_plus_four() -> number(). 9 | two_plus_four() -> module_info_higher_arity:module_info(2, 4). -------------------------------------------------------------------------------- /test/should_pass/named_fun_infer_pass.erl: -------------------------------------------------------------------------------- 1 | -module(named_fun_infer_pass). 2 | 3 | -export([atom_sum/1]). 4 | 5 | %% Documents expected behaviour that the type of parameters are not inferred 6 | %% when infer is on in do_type_check_expr. 7 | -spec atom_sum([atom()]) -> integer(). 8 | atom_sum(Atoms) -> 9 | F = fun Sum(Acc, [Int | Rest]) -> 10 | Sum(Acc + Int, Rest); 11 | Sum(Acc, []) -> 12 | Acc 13 | end, 14 | F(Atoms, 0). 15 | -------------------------------------------------------------------------------- /test/should_pass/named_fun_pass.erl: -------------------------------------------------------------------------------- 1 | -module(named_fun_pass). 2 | 3 | -export([fac/1]). 4 | 5 | -spec fac(integer()) -> integer(). 6 | fac(I) -> 7 | F = fun Fac(0) -> 8 | 1; 9 | Fac(N) -> 10 | N*Fac(N-1) 11 | end, 12 | F(I). 13 | -------------------------------------------------------------------------------- /test/should_pass/negate_none.erl: -------------------------------------------------------------------------------- 1 | -module(negate_none). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec crash() -> none(). 6 | crash() -> error(crash). 7 | 8 | foo() -> -crash(). 9 | 10 | %-spec bar(none()) -> _. 11 | %bar(X) -> -X. %% <--- error "clause cannot be reached" 12 | 13 | -------------------------------------------------------------------------------- /test/should_pass/nested_pattern_match.erl: -------------------------------------------------------------------------------- 1 | -module(nested_pattern_match). 2 | -compile([debug_info]). 3 | -export([f/1]). 4 | 5 | -record(s, {a :: c | d}). 6 | -record(r, {a :: #s{}}). 7 | 8 | -spec f(#r{}) -> boolean(). 9 | f(R) -> if 10 | (R#r.a)#s.a == c -> true; 11 | true -> false 12 | end. 13 | -------------------------------------------------------------------------------- /test/should_pass/non_neg_plus_pos_is_pos_pass.erl: -------------------------------------------------------------------------------- 1 | -module(non_neg_plus_pos_is_pos_pass). 2 | 3 | -export([f/1]). 4 | 5 | -spec f(non_neg_integer()) -> pos_integer(). 6 | f(N) -> 7 | h(N + 1). 8 | 9 | -spec g(non_neg_integer()) -> pos_integer(). 10 | g(N) -> 11 | h(1 + N). 12 | 13 | -spec h(pos_integer()) -> pos_integer(). 14 | h(P) -> P. 15 | -------------------------------------------------------------------------------- /test/should_pass/nonempty_cons.erl: -------------------------------------------------------------------------------- 1 | -module(nonempty_cons). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec t() -> [integer(), ...]. 6 | t() -> 7 | [1]. 8 | -------------------------------------------------------------------------------- /test/should_pass/nonempty_list_match_in_head_exhaustive.erl: -------------------------------------------------------------------------------- 1 | -module(nonempty_list_match_in_head_exhaustive). 2 | 3 | -export([f/1]). 4 | 5 | -type t() :: {} | [t()]. 6 | 7 | -spec f(t()) -> ok. 8 | f({}) -> ok; 9 | f([]) -> ok; 10 | f([_|_]) -> ok. 11 | -------------------------------------------------------------------------------- /test/should_pass/nonempty_string.erl: -------------------------------------------------------------------------------- 1 | -module(nonempty_string). 2 | 3 | -export([foo/0]). 4 | 5 | -spec foo() -> nonempty_string(). 6 | foo() -> "banana". 7 | -------------------------------------------------------------------------------- /test/should_pass/nonexhaustive_record_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(nonexhaustive_record_pattern). 2 | 3 | -export([f/2, 4 | g/2]). 5 | 6 | -record(env, {}). 7 | -type env() :: #env{}. 8 | 9 | -spec f(_, env()) -> ok. 10 | f([], _Env) -> ok; 11 | f([_Ty1|_Tys], _Env) -> ok. 12 | 13 | -spec g(integer(), env()) -> ok. 14 | g(1, _Env) -> ok; 15 | g(_I, _Env) -> ok. 16 | -------------------------------------------------------------------------------- /test/should_pass/opaque.erl: -------------------------------------------------------------------------------- 1 | -module(opaque). 2 | 3 | -export([external/2, external_update/0, internal/2, use_internal/2, external_return/0]). 4 | -export_type([my_opaque/0]). 5 | 6 | -spec external(user_types:my_opaque(), integer() | undefined) -> integer(). 7 | external(_, undefined) -> 0; 8 | external(_, I) -> I. 9 | 10 | -spec external_update() -> ok. 11 | external_update() -> 12 | Val = user_types:new_opaque(), 13 | _Val2 = user_types:update_opaque(Val), 14 | ok. 15 | 16 | -spec external_return() -> user_types:my_opaque(). 17 | external_return() -> user_types:new_opaque(). 18 | 19 | -opaque my_opaque() :: integer(). 20 | 21 | -spec internal(my_opaque(), integer() | undefined) -> integer(). 22 | internal(_, undefined) -> 0; 23 | internal(_, I) -> I. 24 | 25 | -spec use_internal(my_opaque(), integer() | undefined) -> integer(). 26 | use_internal(I, undefined) -> I; 27 | use_internal(_, I) -> I. 28 | -------------------------------------------------------------------------------- /test/should_pass/operator_pattern_pass.erl: -------------------------------------------------------------------------------- 1 | -module(operator_pattern_pass). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f1(float()) -> {}. 6 | f1(1.0+1.0) -> {}; 7 | f1(12.5-20.3) -> {}; 8 | f1(1.2*1.3) -> {}; 9 | f1(1.5/0.5) -> {}; 10 | f1(4/3) -> {}; 11 | f1(+0.3) -> {}; 12 | f1(-4.13) -> {}; 13 | f1(_) -> {}. 14 | 15 | f2(1.0+1.0) -> {}; 16 | f2(12.5-20.3) -> {}; 17 | f2(1.2*1.3) -> {}; 18 | f2(1.5/0.5) -> {}; 19 | f2(4/3) -> {}; 20 | f2(+0.3) -> {}; 21 | f2(-4.13) -> {}; 22 | f2(_) -> {}. 23 | 24 | -spec n1(non_neg_integer()) -> {}. 25 | n1(1+1) -> {}; 26 | n1(13-12) -> {}; 27 | n1(10*3) -> {}; 28 | n1(-3*-4) -> {}; 29 | n1(+5) -> {}; 30 | n1(-(-7)) -> {}; 31 | n1(_) -> {}. 32 | 33 | n2(1+1) -> {}; 34 | n2(13-12) -> {}; 35 | n2(10*3) -> {}; 36 | n2(-3*-4) -> {}; 37 | n2(+5) -> {}; 38 | n2(-(-7)) -> {}; 39 | n2(_) -> {}. 40 | 41 | -spec i1(integer()) -> {}. 42 | i1(1+1) -> {}; 43 | i1(1-5) -> {}; 44 | i1(3*-5) -> {}; 45 | i1(+10) -> {}; 46 | i1(-4) -> {}; 47 | i1(_) -> {}. 48 | 49 | i2(1+1) -> {}; 50 | i2(1-5) -> {}; 51 | i2(3*-5) -> {}; 52 | i2(+10) -> {}; 53 | i2(-4) -> {}; 54 | i2(_) -> {}. 55 | 56 | -spec p1(pos_integer()) -> {}. 57 | p1(1+1) -> {}; 58 | p1(2-1) -> {}; 59 | p1(2*2) -> {}; 60 | p1(+3) -> {}; 61 | p1(-(-6)) -> {}; 62 | p1(_) -> {}. 63 | 64 | p2(1+1) -> {}; 65 | p2(2-1) -> {}; 66 | p2(2*2) -> {}; 67 | p2(+3) -> {}; 68 | p2(-(-6)) -> {}; 69 | p2(_) -> {}. 70 | -------------------------------------------------------------------------------- /test/should_pass/other_module.erl: -------------------------------------------------------------------------------- 1 | -module(other_module). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% these exported types are used in remote_types module 6 | -export_type([my_generic/1, nested_generic/1]). 7 | 8 | % -type other_module_type() :: other_module. 9 | 10 | %% v-- Either a local type (my_tuple) to this module or a remote user type 11 | -type my_generic(RetType) :: my_shadowed_type() | {ok, RetType}. 12 | %% A type to be shadowed by a type with the same name in a local module. 13 | %% To be used in `user_types:my_generic(my_shadowed_type())` 14 | -type my_shadowed_type() :: other_module. 15 | 16 | -type nested_generic(RetType) :: nested_generic | my_generic(RetType). 17 | -------------------------------------------------------------------------------- /test/should_pass/pattern_bind_reuse.erl: -------------------------------------------------------------------------------- 1 | -module(pattern_bind_reuse). 2 | 3 | -export([test/2, guess_the_die/1, is_same/2, record/2]). 4 | 5 | -spec test(integer() | undefined, integer()) -> integer(). 6 | test(I, I) -> I + I; 7 | test(_, I) -> I. 8 | 9 | %% Guess a number. Roll a die. If same, you win the points of the die. 10 | -spec guess_the_die(Guess :: integer()) -> Score :: 0..6. 11 | guess_the_die(Guess) -> 12 | %% Match any() against a bound variable of type integer() 13 | case roll_die() of 14 | Guess -> 15 | Guess; % :: integer(), but should be any() & integer() 16 | _WrongGuess -> 17 | 0 18 | end. 19 | 20 | %% Untyped helper; returns 1..6 21 | roll_die() -> 22 | rand:uniform(6). 23 | 24 | -spec is_same(integer(), integer()) -> boolean(). 25 | is_same(N, N) -> 26 | true; 27 | is_same(_, _) -> 28 | %% False error: This clause can't be reached 29 | false. 30 | 31 | -record(r, { f :: integer() | undefined }). 32 | 33 | -spec record(#r{}, integer()) -> integer(). 34 | record(#r{f = I}, I) -> I; 35 | record(#r{f = undefined}, I) -> I; 36 | record(#r{f = I}, _) -> I. 37 | 38 | -------------------------------------------------------------------------------- /test/should_pass/pattern_record.erl: -------------------------------------------------------------------------------- 1 | -module(pattern_record). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -record(test, { 6 | field :: integer() 7 | }). 8 | 9 | -spec clause_match(#test{} | whatever) -> #test{} | not_test. 10 | clause_match(#test{} = Test) -> Test; 11 | clause_match(_) -> not_test. 12 | 13 | -spec clause_match2(#test{} | whatever) -> #test{} | not_test. 14 | clause_match2(Test = #test{}) -> Test; 15 | clause_match2(_) -> not_test. 16 | 17 | -spec pat_match(#test{} | whatever) -> #test{} | not_test. 18 | pat_match(T) -> 19 | case T of 20 | #test{} = Test -> Test; 21 | _ -> not_test 22 | end. 23 | 24 | -spec pat_match2(#test{} | whatever) -> #test{} | not_test. 25 | pat_match2(T) -> 26 | case T of 27 | Test = #test{} -> Test; 28 | _ -> not_test 29 | end. 30 | 31 | -record(r1, { 32 | f :: integer() 33 | }). 34 | 35 | -record(r2, { 36 | f :: atom() 37 | }). 38 | 39 | -spec good(#r1{} | #r2{}) -> integer(). 40 | good(R = #r1{f = F}) -> R#r1.f + F; 41 | good(_) -> 0. 42 | 43 | -record(r3, { 44 | r :: #r1{} | #r2{} 45 | }). 46 | 47 | -spec multiple(#r3{} | #r2{}) -> integer(). 48 | multiple(#r3{r = R = #r1{}}) -> R#r1.f; 49 | multiple(_) -> 0. 50 | -------------------------------------------------------------------------------- /test/should_pass/pattern_with_ty_vars.erl: -------------------------------------------------------------------------------- 1 | -module(pattern_with_ty_vars). 2 | 3 | -export([foo/1]). 4 | 5 | %% _X is assigned a type variable, e.g. _TyVar-57293823 6 | %% Simply check that fun arg type T is matching {a, _TyVar-57293823} 7 | %% and that the typechecker doesn't crash. 8 | -spec foo([{a|b, a|b}]) -> boolean(). 9 | foo(Xs) -> 10 | lists_any(fun ({a, _X}) -> false; 11 | ({b, _Y}) -> true 12 | end, 13 | Xs). 14 | 15 | %% An isolated version of lists:any/2 16 | -spec lists_any(fun((T) -> boolean()), [T]) -> boolean(). 17 | lists_any(Pred, [X|Xs]) -> Pred(X) orelse lists_any(Pred, Xs); 18 | lists_any(_Pred, []) -> false. 19 | -------------------------------------------------------------------------------- /test/should_pass/poly_lists_map_constraints_pass.erl: -------------------------------------------------------------------------------- 1 | -module(poly_lists_map_constraints_pass). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | %% This is a minimised problem from type checking `gradualizer_db:collect_specs/1' 6 | %% with constraint solving enabled. 7 | -export([map_many/1]). 8 | 9 | -export([j/1]). 10 | 11 | %% We cannot preserve the nonempty-property across calls to lists:map/2 anymore :( 12 | %% This interferes with the clause choice when calling an intersection-typed function. 13 | -spec map_many({_, nonempty_list()}) -> list(). 14 | map_many({_Key, Types}) -> 15 | lists:map(fun map_elem/1, 16 | map_specific_list(Types)). 17 | 18 | -spec map_elem([A]) -> [A]; 19 | (a | b) -> a. 20 | map_elem(L) when is_list(L) -> L; 21 | map_elem(a) -> a; 22 | map_elem(b) -> a. 23 | 24 | -type t_list() :: [a | b]. 25 | 26 | -spec map_specific_list(t_list()) -> t_list(). 27 | map_specific_list(List) -> 28 | lists:map(fun (E) -> E end, List). 29 | 30 | -spec j([binary() | integer()]) -> list(). 31 | j(L) -> 32 | lists:map(fun has_intersection_spec/1, L). 33 | 34 | -spec has_intersection_spec(binary()) -> binary(); 35 | (integer()) -> integer(). 36 | has_intersection_spec(B) when is_binary(B) -> B; 37 | has_intersection_spec(I) when is_integer(I) -> I. 38 | -------------------------------------------------------------------------------- /test/should_pass/poly_lists_map_pass.erl: -------------------------------------------------------------------------------- 1 | -module(poly_lists_map_pass). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([f/1, 6 | k/1]). 7 | 8 | -spec f([integer()]) -> [integer()]. 9 | f(L) -> 10 | lists:map(fun (I) -> I * 2 end, L). 11 | 12 | -spec k([binary() | integer()]) -> [integer()]. 13 | k(L) -> 14 | lists:map(fun 15 | (I) when is_integer(I) -> I * 2; 16 | (B) when is_binary(B) -> binary_to_integer(B) 17 | end, L). 18 | -------------------------------------------------------------------------------- /test/should_pass/poly_map_pattern.erl: -------------------------------------------------------------------------------- 1 | -module(poly_map_pattern). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([map_type_var/1]). 6 | 7 | -spec map_type_var(nonempty_list(#{atom() => integer()})) -> integer(). 8 | map_type_var(L) -> 9 | V = lists:nth(2, L), 10 | %% at this point V :: T 11 | case V of 12 | %% pattern matching a map against a type var 13 | #{k := Int} -> 14 | Int 15 | end. 16 | -------------------------------------------------------------------------------- /test/should_pass/poly_pass_infer.erl: -------------------------------------------------------------------------------- 1 | -module(poly_pass_infer). 2 | 3 | -export([ 4 | all_positive/1 5 | ]). 6 | 7 | -gradualizer([solve_constraints]). 8 | 9 | -spec all_positive([integer()]) -> {true, [integer()]} | false. 10 | all_positive(List) -> 11 | F = fun 12 | (Num, {true, Positive}) -> 13 | case Num > 0 of 14 | true -> {true, [Num | Positive]}; 15 | false -> false 16 | end; 17 | (_Num, false) -> false 18 | end, 19 | lists:foldl(F, {true, []}, List). 20 | -------------------------------------------------------------------------------- /test/should_pass/poly_pass_no_solve_constraints.erl: -------------------------------------------------------------------------------- 1 | -module(poly_pass_no_solve_constraints). 2 | 3 | %% The `-gradualizer([solve_constraints])' is intentionally missing. 4 | -compile([export_all, nowarn_export_all]). 5 | 6 | -spec id(A) -> A. 7 | id(X) -> X. 8 | 9 | -spec f(apple) -> banana. 10 | f(X) -> id(X). 11 | 12 | -spec g(boolean()) -> binary(). 13 | g(Bool) -> 14 | NewBool = id(Bool), 15 | NewBool. 16 | -------------------------------------------------------------------------------- /test/should_pass/poly_union_lower_bound_pass.erl: -------------------------------------------------------------------------------- 1 | -module(poly_union_lower_bound_pass). 2 | 3 | -gradualizer([solve_constraints]). 4 | 5 | -export([f/1, 6 | g/1, 7 | i/1]). 8 | 9 | -type t1() :: {_, _, _, _}. 10 | -type t2() :: {_, _, _}. 11 | 12 | -spec f([t1() | t2()]) -> ok. 13 | f(Fields) -> 14 | lists:flatmap(fun 15 | ({_, _, _, _}) -> [t1]; 16 | ({_, _, _}) -> [t2] 17 | end, Fields), 18 | ok. 19 | 20 | -spec g([t1() | t2()]) -> ok. 21 | g(Fields) -> 22 | lists:foldl(fun 23 | ({_, _, _, _}, Acc) -> [t1 | Acc]; 24 | (_, Acc) -> [default | Acc] 25 | end, [], Fields), 26 | ok. 27 | 28 | -spec i([binary() | integer()]) -> [integer()]. 29 | i(L) -> 30 | lists:map(fun takes_a_union/1, L). 31 | 32 | -spec takes_a_union(binary() | integer()) -> integer(). 33 | takes_a_union(B) when is_binary(B) -> binary_to_integer(B); 34 | takes_a_union(I) when is_integer(I) -> I. 35 | -------------------------------------------------------------------------------- /test/should_pass/preludes.erl: -------------------------------------------------------------------------------- 1 | -module(preludes). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% use lists:sort (which only accepts list() not all term()) to test 6 | %% that V* is any() 7 | app_get_env() -> 8 | {ok, V1} = application:get_env(param), 9 | lists:sort(V1), 10 | {ok, V2} = application:get_env(app, param), 11 | lists:sort(V2), 12 | V3 = application:get_env(app, param, default), 13 | lists:sort(V3). 14 | -------------------------------------------------------------------------------- /test/should_pass/qlc_test.erl: -------------------------------------------------------------------------------- 1 | -module(qlc_test). 2 | 3 | -export([test/0]). 4 | 5 | % This example is from the documentation of qlc 6 | 7 | test() -> 8 | L = [1,2,3], 9 | Bs = erl_eval:add_binding('L', L, erl_eval:new_bindings()), 10 | QHE = qlc:string_to_handle("[X+1 || X <- L].", [], Bs), 11 | case QHE of 12 | {error, Module, Reason} -> 13 | io:format("Error in ~p: ~p~n",[Module, Reason]); 14 | QH -> qlc:eval(QH) 15 | end. 16 | -------------------------------------------------------------------------------- /test/should_pass/record_info.erl: -------------------------------------------------------------------------------- 1 | -module(record_info). 2 | 3 | -export([fields/0, size/0]). 4 | 5 | -record(fruitz, {ananas, bananas :: atom(), cananas = 2}). 6 | 7 | -spec fields() -> [ananas | bananas | cananas]. 8 | fields() -> record_info(fields, fruitz). 9 | 10 | -spec size() -> 4. 11 | size() -> 12 | N = record_info(size, fruitz), 13 | N. 14 | -------------------------------------------------------------------------------- /test/should_pass/record_union_pass.erl: -------------------------------------------------------------------------------- 1 | -module(record_union_pass). 2 | 3 | -export([f/0, 4 | g/0, 5 | h/0]). 6 | 7 | -record(r, {}). 8 | 9 | -type r() :: #r{}. 10 | 11 | -spec f() -> r() | undefined. 12 | f() -> 13 | #r{}. 14 | 15 | -record(s, {}). 16 | 17 | -record(t, {field1 :: integer()}). 18 | 19 | -type u() :: #r{} | #s{}. 20 | 21 | -spec g() -> u() | #t{} | undefined. 22 | g() -> 23 | #s{}. 24 | 25 | -spec h() -> #t{}. 26 | h() -> 27 | helper(#t{field1 = 1}). 28 | 29 | -spec helper(#t{}) -> #t{}. 30 | helper(T) -> T. 31 | -------------------------------------------------------------------------------- /test/should_pass/record_union_with_any_should_pass.erl: -------------------------------------------------------------------------------- 1 | -module(record_union_with_any_should_pass). 2 | 3 | -export([f/1, 4 | g/0]). 5 | 6 | -record(r, {field}). 7 | 8 | -spec f(_) -> #r{} | any(). 9 | f(R) -> 10 | R#r{field = val}. 11 | 12 | -spec g() -> #r{} | any(). 13 | g() -> 14 | #r{field = val}. 15 | -------------------------------------------------------------------------------- /test/should_pass/record_var.erl: -------------------------------------------------------------------------------- 1 | -module(record_var). 2 | 3 | -export([f/1, g/0]). 4 | 5 | -record(rec, {apa}). 6 | 7 | -spec f(A) -> A when A :: #rec{}. 8 | f(#rec{}) -> #rec{}. 9 | 10 | %% also defined in records.erl - with different line numbers 11 | -record(r, {f1 :: atom(), 12 | f2 = 1 :: integer()}). 13 | 14 | %% The type of R is #r{} from the records module. As the record 15 | %% definition and types are the same in the two modules the records 16 | %% must be compatible. 17 | g() -> 18 | R = records:h(), 19 | R#r.f1. 20 | -------------------------------------------------------------------------------- /test/should_pass/record_wildcard_pass.erl: -------------------------------------------------------------------------------- 1 | -module(record_wildcard_pass). 2 | 3 | -export([f/0, g/0, h/1]). 4 | 5 | -record(rec, {apa = 1 :: integer() 6 | ,bepa = false :: boolean() 7 | ,cepa = undefined :: atom() 8 | }). 9 | 10 | f() -> 11 | #rec{ apa = 1 12 | , _ = true }. 13 | 14 | -spec g() -> #rec{}. 15 | g() -> 16 | #rec{ apa = 1 17 | , _ = true }. 18 | 19 | %% wildcard in pattern matching 20 | -spec h(#rec{}) -> boolean(). 21 | h(#rec{apa = 1, _ = true}) -> 22 | true; 23 | h(_) -> 24 | false. 25 | -------------------------------------------------------------------------------- /test/should_pass/record_with_user_defined.erl: -------------------------------------------------------------------------------- 1 | -module(record_with_user_defined). 2 | -export([foo/2]). 3 | 4 | -type pred() :: fun((term()) -> boolean()). 5 | 6 | -record(state, {pred :: pred()}). 7 | 8 | -spec foo(term(), #state{}) -> boolean(). 9 | foo(Event, State) -> 10 | #state{pred = Pred} = State, 11 | Pred(Event). 12 | -------------------------------------------------------------------------------- /test/should_pass/records.erl: -------------------------------------------------------------------------------- 1 | -module(records). 2 | 3 | -export([f/0, f/1, g/1, h/0, i/0, j/0, k/0, l/0, 4 | rec_field_subtype/1, 5 | rec_index_subtype/0, 6 | record_as_tuple/1, 7 | nospec_update_bug/1]). 8 | 9 | -record(r, {f1 :: atom(), 10 | f2 = 1 :: integer()}). 11 | 12 | f() -> 13 | %% record creation 14 | R = #r{f2 = 2}, 15 | %% record update 16 | R2 = R#r{f2 = 3}, 17 | %% record field access 18 | R2#r.f2. 19 | 20 | 21 | -spec g(#r{}) -> #r{}. 22 | g(R) -> 23 | R#r{ f2 = R#r.f2 }. 24 | 25 | -spec h() -> #r{}. 26 | h() -> 27 | #r{ f1 = apa, f2 = 3 }. 28 | 29 | i() -> 30 | #r.f1. 31 | 32 | -spec j() -> 3. 33 | j() -> 34 | #r.f2. 35 | 36 | -record(test_k, { 37 | field :: integer() | undefined 38 | }). 39 | 40 | -spec k() -> integer() | undefined. 41 | k() -> 42 | Test = #test_k{}, 43 | Test#test_k.field. 44 | 45 | -record(test_l, { 46 | field = 0 :: integer() 47 | }). 48 | 49 | -spec l() -> integer(). 50 | l() -> 51 | Test = #test_l{}, 52 | Test#test_l.field. 53 | 54 | -spec rec_field_subtype(#r{}) -> number(). 55 | rec_field_subtype(R) -> 56 | R#r.f2. 57 | 58 | -spec rec_index_subtype() -> number(). 59 | rec_index_subtype() -> 60 | #r.f2. 61 | 62 | -spec record_as_tuple(#r{}) -> tuple(). 63 | record_as_tuple(R) -> 64 | R. 65 | 66 | -record(rec_any, {f}). 67 | f(#rec_any{f = F} = R) -> F. 68 | 69 | -record(nospec_update_bug, { 70 | a :: integer(), 71 | b :: integer() 72 | }). 73 | 74 | nospec_update_bug(Rec) -> 75 | Rec#nospec_update_bug{ 76 | b = 0 77 | }. 78 | -------------------------------------------------------------------------------- /test/should_pass/recursive_call_with_remote_union_return_type_pass.erl: -------------------------------------------------------------------------------- 1 | -module(recursive_call_with_remote_union_return_type_pass). 2 | 3 | -export([recursive/0]). 4 | 5 | -spec recursive() -> ok | user_types:my_union(). 6 | recursive() -> 7 | recursive(). 8 | -------------------------------------------------------------------------------- /test/should_pass/recursive_types_passing.erl: -------------------------------------------------------------------------------- 1 | -module(recursive_types_passing). 2 | 3 | -export([recursive_param1/1, 4 | return_recursive_type/1, 5 | recursive_param2/1]). 6 | 7 | 8 | -type apa(A) :: A | apa({A}). 9 | 10 | -spec recursive_param1(apa(integer())) -> ok. 11 | recursive_param1(_) -> ok. 12 | 13 | 14 | -record(rec, {rec :: any()}). 15 | -type rec(A) :: A | #rec{rec :: A | rec(A)}. 16 | 17 | -spec return_recursive_type(any()) -> rec(integer()). 18 | return_recursive_type(_) -> 1. 19 | 20 | 21 | -type rec1(A) :: A | rec1({A | rec1(A)}). 22 | 23 | -spec recursive_param2(rec1(integer())) -> ok. 24 | recursive_param2(_) -> ok. 25 | -------------------------------------------------------------------------------- /test/should_pass/refine_comparison.erl: -------------------------------------------------------------------------------- 1 | -module(refine_comparison). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec comp1(integer()) -> pos_integer() | neg_integer(). 6 | comp1(N) when 0 < N -> N; 7 | comp1(N) when 0 > N -> N; 8 | comp1(_) -> 1. 9 | 10 | -spec comp2(2..10) -> 2..5 | 8..10. 11 | comp2(N) when N =< 5 -> N; 12 | comp2(N) when N >= 8 -> N; 13 | comp2(_) -> 10. 14 | 15 | -spec comp3(1..3) -> 1 | 3. 16 | comp3(N) when N /= 2 -> N; 17 | comp3(_) -> 1. 18 | 19 | -spec comp4(number()) -> 1. 20 | comp4(N) when N =:= 1 -> N; 21 | comp4(_) -> 1. 22 | 23 | -spec compatom1(a | b | c) -> a | b | ok. 24 | compatom1(X) when X == a -> X; 25 | compatom1(X) when X =:= b -> X; 26 | compatom1(_) -> ok. 27 | 28 | -spec compatom2(a | b | c | pid()) -> a | c | pid(). 29 | compatom2(X) when X =/= b -> X; 30 | compatom2(_) -> a. 31 | 32 | -spec compatom3(a | b | c | pid()) -> a | c | pid(). 33 | compatom3(X) when X /= b -> X; 34 | compatom3(_) -> a. 35 | 36 | -type my_map() :: #{value := integer() | nil}. 37 | 38 | -spec comp_map_value1(my_map()) -> integer(). 39 | comp_map_value1(State) when map_get(value, State) /= nil -> 40 | Val = map_get(value, State), 41 | Val + 1; 42 | comp_map_value1(#{value := nil}) -> 0. 43 | 44 | -spec comp_map_value2(my_map()) -> integer(). 45 | comp_map_value2(#{value := Val}) when Val /= nil -> 46 | Val + 1; 47 | comp_map_value2(#{value := nil}) -> 0. 48 | -------------------------------------------------------------------------------- /test/should_pass/refine_mismatch_using_guard_bifs.erl: -------------------------------------------------------------------------------- 1 | -module(refine_mismatch_using_guard_bifs). 2 | 3 | -export([refine_by_guards_1/1, 4 | refine_by_guards_2/1]). 5 | 6 | -spec refine_by_guards_1(atom() | pos_integer() | float() | 7 | binary() | tuple() | [any()] | 8 | map() | pid() | reference() | port()) -> ok | port(). 9 | refine_by_guards_1(X) when is_atom(X) -> ok; 10 | refine_by_guards_1(X) when is_number(X) -> ok; 11 | refine_by_guards_1(X) when is_bitstring(X) -> ok; 12 | refine_by_guards_1(X) when is_tuple(X) -> ok; 13 | refine_by_guards_1(X) when is_list(X) -> ok; 14 | refine_by_guards_1(X) when is_map(X) -> ok; 15 | refine_by_guards_1(X) when is_pid(X) -> ok; 16 | refine_by_guards_1(X) when is_reference(X) -> ok; 17 | refine_by_guards_1(X) -> X. 18 | 19 | -spec refine_by_guards_2(neg_integer() | 2..4 | boolean() | 20 | [a, ...] | port() | ok) -> ok. 21 | refine_by_guards_2(X) when is_integer(X) -> ok; 22 | refine_by_guards_2(X) when is_boolean(X) -> ok; 23 | refine_by_guards_2(X) when is_list(X) -> ok; 24 | refine_by_guards_2(X) when is_port(X) -> ok; 25 | refine_by_guards_2(X) -> X. 26 | -------------------------------------------------------------------------------- /test/should_pass/remote_types_pass.erl: -------------------------------------------------------------------------------- 1 | -module(remote_types_pass). 2 | 3 | -type options() :: proplists:proplist(). 4 | 5 | -spec f(options()) -> ok. 6 | f(Opts) -> 7 | OptsForModule = h() ++ Opts, 8 | g(OptsForModule). 9 | 10 | -spec g(proplists:proplist()) -> ok. 11 | g(_Opts) -> ok. 12 | 13 | -spec h() -> options(). 14 | h() -> []. 15 | -------------------------------------------------------------------------------- /test/should_pass/return_fun.erl: -------------------------------------------------------------------------------- 1 | -module(return_fun). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec return_fun_term() -> term(). 6 | return_fun_term() -> fun nil/0. 7 | 8 | -spec return_fun_fun() -> fun(). 9 | return_fun_fun() -> fun nil/0. 10 | 11 | -spec return_fun_union() -> integer() | fun(). 12 | return_fun_union() -> fun nil/0. 13 | 14 | -spec return_fun_any_arity() -> fun((...) -> list()). 15 | return_fun_any_arity() -> fun nil/0. 16 | 17 | -spec return_fun_intersection() -> fun((any()) -> integer()). 18 | return_fun_intersection() -> fun number/1. 19 | 20 | -spec return_fun_union_intersection() 21 | -> fun((atom()) -> atom()) | 22 | fun((1..3) -> integer()). 23 | return_fun_union_intersection() -> fun number/1. 24 | 25 | -spec nil() -> []. 26 | nil() -> []. 27 | 28 | -spec number(integer()) -> integer(); 29 | (float()) -> float(). 30 | number(N) -> N. 31 | -------------------------------------------------------------------------------- /test/should_pass/rigid_type_variables.erl: -------------------------------------------------------------------------------- 1 | -module(rigid_type_variables). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec id(A) -> A. 6 | id(X) -> X. 7 | 8 | -spec id_same_var(A) -> A. 9 | id_same_var(A) -> A. 10 | 11 | -spec make_tuple(A, B) -> {A, B}. 12 | make_tuple(X, Y) -> {X, Y}. 13 | 14 | -spec fst({P, any()}) -> P. 15 | fst({A, _B}) -> A. 16 | 17 | -spec compose(fun((A) -> B), fun((B) -> C), A) -> C. 18 | compose(F, G, X) -> G(F(X)). 19 | 20 | -spec curry(fun(({A, B}) -> C)) -> fun((A, B) -> C). 21 | curry(F) -> fun (X, Y) -> F({X, Y}) end. 22 | 23 | -spec head([X, ...]) -> X. 24 | head([X | _]) -> X. 25 | 26 | -spec join([A], [B]) -> [A | B]. 27 | join(Xs, Ys) -> Xs ++ Ys. 28 | 29 | -spec prepend([A], A) -> [A]. 30 | prepend(Xs, Y) -> [Y | Xs]. 31 | 32 | -spec filter([A], fun((A) -> boolean())) -> [A]. 33 | filter([], _F) -> []; 34 | filter([X | Xs], F) -> 35 | case F(X) of 36 | true -> [X | filter(Xs, F)]; 37 | false -> filter(Xs, F) 38 | end. 39 | 40 | -spec map([A], fun((A) -> B)) -> [B]. 41 | map([], _F) -> []; 42 | map([X | Xs], F) -> [F(X) | map(Xs, F)]. 43 | 44 | % gets rewritten to (integer()) -> integer() 45 | -spec rewrite_bound(Int) -> Int when Int :: integer(). 46 | rewrite_bound(N) -> N + 1. 47 | 48 | -spec id_with_bound(A) -> R when R :: A. 49 | id_with_bound(X) -> X. 50 | 51 | -type tagged(Value) :: {tag, Value}. 52 | % gets rewritten to (map()) -> {tag, map()} 53 | -spec add_tag(Value) -> tagged(Value) when Value :: map(). 54 | add_tag(Value) -> 55 | {tag, Value}. 56 | 57 | % this one passes only due to the lack of support for bounded quantification 58 | % and this behaviour may (or may not) be considered undesirable 59 | -spec add_tag2(Value) -> {tag, Value} when Value :: tuple(). 60 | add_tag2(_Value) -> 61 | {tag, {apple, banana}}. 62 | -------------------------------------------------------------------------------- /test/should_pass/rigid_type_variables_pass.erl: -------------------------------------------------------------------------------- 1 | -module(rigid_type_variables_pass). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec id(A) -> A. 6 | id(X) -> X. 7 | 8 | %% Used to cause a crash in int_type_to_range/1 on {var, _, 'A'}. 9 | foo() -> -id(42). 10 | -------------------------------------------------------------------------------- /test/should_pass/scope.erl: -------------------------------------------------------------------------------- 1 | -module(scope). 2 | 3 | -compile([export_all, nowarn_export_all, nowarn_unused_vars]). 4 | 5 | f(X) -> 6 | case g(X) of 7 | true -> A = 5, 8 | B = 7; 9 | false -> B = 6 10 | end, 11 | % Fails 12 | % A. 13 | B. 14 | 15 | g(_X) -> 16 | true. 17 | 18 | % Fails 19 | % h() -> 20 | % (X = 5) + X. 21 | 22 | t() -> 23 | _ = { X = 5, Y = 4 }, 24 | % Fails 25 | % { X = 5, Y = 4, X }, 26 | X. 27 | -------------------------------------------------------------------------------- /test/should_pass/send_pass.erl: -------------------------------------------------------------------------------- 1 | -module(send_pass). 2 | 3 | -export([foo/2, bar/2]). 4 | 5 | -spec foo(any(), ok) -> ok. 6 | foo(X, Y) -> X ! Y. 7 | 8 | -spec bar(any(), ok) -> ok. 9 | bar(X, Y) -> 10 | A = X ! Y, 11 | A. 12 | -------------------------------------------------------------------------------- /test/should_pass/sets_set.erl: -------------------------------------------------------------------------------- 1 | -module(sets_set). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec add_var(string(), sets:set(string())) -> sets:set(string()). 6 | add_var(Var, Set) -> 7 | sets:add_element(Var, Set). 8 | -------------------------------------------------------------------------------- /test/should_pass/spec_and_fun_clause_intersection_pass.erl: -------------------------------------------------------------------------------- 1 | -module(spec_and_fun_clause_intersection_pass). 2 | 3 | -export([f/1, g/1, g1/1, g2/1, h/1]). 4 | 5 | -spec f(bar) -> bar; 6 | (list()) -> baz | qux. 7 | f(bar) -> bar; 8 | f([]) -> baz; 9 | f([_|_]) -> qux. 10 | 11 | -type t() :: {tag, integer()}. 12 | 13 | %% Order of spec clauses matches the order of patterns in function clauses - this should pass. 14 | -spec g([t()]) -> [string()]; 15 | (t()) -> string(). 16 | g([] = _Types) -> []; 17 | g([_|_] = Types) -> lists:map(fun g/1, Types); 18 | g({tag, _}) -> "". 19 | 20 | %% Order of spec clauses doesn't match the order of patterns 21 | %% in function clauses - this should pass, too. 22 | -spec g1(t()) -> string(); 23 | ([t()]) -> [string()]. 24 | g1([] = _Types) -> []; 25 | g1([_|_] = Types) -> lists:map(fun g1/1, Types); 26 | g1({tag, _}) -> "". 27 | 28 | %% Order of spec clauses doesn't match the order of patterns 29 | %% in function clauses and function clause patterns are mixed - will that pass? 30 | -spec g2(t()) -> string(); 31 | ([t()]) -> [string()]. 32 | g2([] = _Types) -> []; 33 | g2({tag, _}) -> ""; 34 | g2([_|_] = Types) -> lists:map(fun g2/1, Types). 35 | 36 | -spec h(t()) -> string(); 37 | ([t()]) -> [string()]. 38 | h(Types) when is_list(Types) -> lists:map(fun h/1, Types); 39 | h({tag, _}) -> "". 40 | -------------------------------------------------------------------------------- /test/should_pass/stuff_as_top.erl: -------------------------------------------------------------------------------- 1 | -module(stuff_as_top). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -record(rec, {field :: gradualizer:top()}). 6 | 7 | -spec tuple_as_top() -> gradualizer:top(). 8 | tuple_as_top() -> 9 | {x, y, z}. 10 | 11 | -spec tuple_pat_as_top(gradualizer:top()) -> any(). 12 | tuple_pat_as_top({X, Y}) -> 13 | {Y, X}. 14 | 15 | -spec cons_as_top() -> gradualizer:top(). 16 | cons_as_top() -> [x, y]. 17 | 18 | -spec cons_pat_as_top(gradualizer:top()) -> any(). 19 | cons_pat_as_top([X|_]) -> X. 20 | 21 | -spec fun_as_top() -> gradualizer:top(). 22 | fun_as_top() -> fun (X) -> X + 1 end. 23 | 24 | -spec record_as_top() -> gradualizer:top(). 25 | record_as_top() -> #rec{}. 26 | 27 | -spec record_pat_as_top(gradualizer:top()) -> any(). 28 | record_pat_as_top(#rec{}) -> ok. 29 | -------------------------------------------------------------------------------- /test/should_pass/try.erl: -------------------------------------------------------------------------------- 1 | -module('try'). 2 | 3 | -compile([export_all, nowarn_export_all, nowarn_unused_vars]). 4 | 5 | -spec t() -> 2..4. 6 | t()-> 7 | try A = 1 of 8 | 1 -> 9 | %% a:b(A), unsafe 10 | B = 2 11 | catch _:_ -> 12 | %% a:b(A), unsafe 13 | %% a:b(B), unbound 14 | C = 3 15 | after 16 | %% a:b(A), unsafe 17 | %% a:b(B), unsafe 18 | %% a:b(C), unsafe 19 | D = 4 20 | end. 21 | %% A. unsafe 22 | %% B. unsafe 23 | %% C. unsafe 24 | %% D. unsafe 25 | 26 | r()-> 27 | try A = 1 of 28 | 1 -> 29 | %% a:b(A), unsafe 30 | B = 2 31 | catch _:_ -> 32 | %% a:b(A), unsafe 33 | %% a:b(B), unbound 34 | C = 3 35 | after 36 | %% a:b(A), unsafe 37 | %% a:b(B), unsafe 38 | %% a:b(C), unsafe 39 | D = 4 40 | end. 41 | %% A. unsafe 42 | %% B. unsafe 43 | %% C. unsafe 44 | %% D. unsafe 45 | 46 | s() -> 47 | try ok of 48 | ok -> 49 | ok 50 | catch _:_ -> 51 | nok 52 | end. 53 | -------------------------------------------------------------------------------- /test/should_pass/try_expr.erl: -------------------------------------------------------------------------------- 1 | -module(try_expr). 2 | 3 | -export([plain_try/2, plain_try_var/2, try_of/4, try_of_var/4]). 4 | 5 | -spec may_fail(1 | 2 | 3) -> boolean(). 6 | may_fail(1) -> true; 7 | may_fail(2) -> false; 8 | may_fail(3) -> throw(got_three). 9 | 10 | -spec plain_try(1 | 2 | 3, float()) -> boolean() | float(). 11 | plain_try(Key, Float) -> 12 | try may_fail(Key) 13 | catch 14 | got_three -> Float 15 | end. 16 | 17 | -spec plain_try_var(1 | 2 | 3, float()) -> boolean() | float(). 18 | plain_try_var(Key, Float) -> 19 | X = try may_fail(Key) 20 | catch 21 | got_three -> Float 22 | end, 23 | X. 24 | 25 | -spec try_of(1 | 2 | 3, float(), one, two) -> one | two | float(). 26 | try_of(Key, Float, OneAtom, TwoAtom) -> 27 | try may_fail(Key) of 28 | true -> OneAtom; 29 | false -> TwoAtom 30 | catch 31 | got_three -> Float 32 | end. 33 | 34 | -spec try_of_var(1 | 2 | 3, float(), one, two) -> one | two | float(). 35 | try_of_var(Key, Float, OneAtom, TwoAtom) -> 36 | X = try may_fail(Key) of 37 | true -> OneAtom; 38 | false -> TwoAtom 39 | catch 40 | got_three -> Float 41 | end, 42 | X. 43 | -------------------------------------------------------------------------------- /test/should_pass/tuple.erl: -------------------------------------------------------------------------------- 1 | -module(tuple). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec tup({any(),any(),any()}) -> any(). 6 | tup({A,_,_}) -> 7 | A. 8 | 9 | tup2(A,B) -> 10 | {A,B}. 11 | 12 | -type tup() :: {atom(), integer()}. 13 | -spec tup3(atom(), integer()) -> tup(). 14 | tup3(A,B) -> 15 | {A,B}. 16 | 17 | %% expect_tuple_type should be able to extract that the type `any() | atom()` expects (accepts) any tuple 18 | %% (There is a shortcut for f(any()) which avoids this code path) 19 | tup4(A) -> 20 | f({A, 1}). 21 | 22 | -spec f(any() | atom()) -> ok. 23 | f(_) -> 24 | ok. 25 | -------------------------------------------------------------------------------- /test/should_pass/tuple_union_pass.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_pass). 2 | 3 | -export([f/0, 4 | g/0]). 5 | 6 | -spec f() -> {integer()} | {boolean()}. 7 | f() -> 8 | {true}. 9 | 10 | -type t() :: {float()} | {binary()}. 11 | 12 | -spec g() -> t() | {integer()} | {boolean()}. 13 | g() -> 14 | {true}. 15 | -------------------------------------------------------------------------------- /test/should_pass/tuple_union_pat.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_pat). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec f(tuple() | integer()) -> ok. 6 | f({1, 2}) -> 7 | ok. 8 | 9 | -spec g({ok, binary()} | {error, term()}) -> integer(). 10 | g({error, key_not_found} = _Response) -> 11 | 1; 12 | g({error, _} = _Response) -> 13 | 2; 14 | g({ok, _} = _Response) -> 15 | 3. 16 | -------------------------------------------------------------------------------- /test/should_pass/tuple_union_pattern_pass.erl: -------------------------------------------------------------------------------- 1 | -module(tuple_union_pattern_pass). 2 | 3 | -export([f/1]). 4 | 5 | -type type() :: af_annotated_type() | af_fun_type(). 6 | 7 | -type anno() :: erl_anno:anno(). 8 | 9 | -type af_anno() :: af_variable(). 10 | 11 | -type af_variable() :: {'var', anno(), atom()}. % | af_anon_variable() 12 | 13 | -type af_annotated_type() :: 14 | {'ann_type', anno(), [af_anno() | type()]}. % [Var, Type] 15 | 16 | -type af_fun_type() :: {'type', anno(), 'fun', []} 17 | | {'type', anno(), 'fun', [{'type', anno(), 'any'} | type()]}. 18 | 19 | -spec f(type()) -> ok. 20 | f({type, _L, 'fun', [_Any = {type, _, any}, _RetTy]}) -> ok. 21 | -------------------------------------------------------------------------------- /test/should_pass/type_decl.erl: -------------------------------------------------------------------------------- 1 | -module(type_decl). 2 | 3 | -export_type([a_type/0, an_opaque_type/0, parameterized/1]). 4 | 5 | -type a_type() :: boolean(). 6 | -opaque an_opaque_type() :: integer(). 7 | 8 | -type parameterized(A) :: {A,A}. 9 | -------------------------------------------------------------------------------- /test/should_pass/type_pattern.erl: -------------------------------------------------------------------------------- 1 | %%% @doc Test cases for `add_type_pat/4' 2 | -module(type_pattern). 3 | 4 | -compile([export_all, nowarn_export_all]). 5 | 6 | -type mychar() :: char(). 7 | 8 | %% The user type `mychar()' inside a list is not normalized when 9 | %% `add_types_pats/4' is called, but postponed later within 10 | %% `subtype(string(), [mychar()], TEnv)'. There was a typo that 11 | %% specifically in case of a string pattern VEnv was passed instead of 12 | %% TEnv. 13 | -spec f([mychar()]) -> any(). 14 | f("foo") -> ok; 15 | f(_) -> also_ok. 16 | 17 | -type ok_tuple() :: {ok}. 18 | 19 | -spec g([ok_tuple()]) -> list(). 20 | g(List) -> 21 | [ok || {ok} <- List]. 22 | -------------------------------------------------------------------------------- /test/should_pass/type_variable.erl: -------------------------------------------------------------------------------- 1 | -module(type_variable). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec fff() -> Result when Result :: integer(). 6 | fff() -> 0. 7 | 8 | ggg() -> -fff(). 9 | 10 | %% Use string:words/1 since its type spec in OTP is 11 | %% -spec words(String) -> Count when 12 | %% String :: string(), 13 | %% Count :: pos_integer(). 14 | 15 | ggg_ext() -> -string:words("a b c d"). 16 | 17 | ggg_fun1() -> -(fun fff/0)(). 18 | ggg_fun2() -> F = fun fff/0, -F(). 19 | 20 | ggg_mfa() -> 21 | F = fun string:words/1, 22 | -F("a b c d"). 23 | 24 | %% Result should be pos_integer() in the end. 25 | -spec ff() -> Result when 26 | Result :: integer() | Intermediate, 27 | Result :: apple | Other, 28 | Intermediate :: boolean() | Other, 29 | Other :: pos_integer(). 30 | ff() -> 1. 31 | 32 | -spec gg() -> neg_integer(). 33 | gg() -> -ff(). 34 | -------------------------------------------------------------------------------- /test/should_pass/type_vars_term.erl: -------------------------------------------------------------------------------- 1 | -module(type_vars_term). 2 | 3 | -export([f/1]). 4 | 5 | %% This fails if we unfold T in id/1 to 6 | %% -spec id(term()) -> term(). 7 | -spec f(integer()) -> integer(). 8 | f(N) -> id(N). 9 | 10 | -spec id(T) -> T when T :: term(). 11 | id(X) -> X. 12 | -------------------------------------------------------------------------------- /test/should_pass/typed_record_field_access.erl: -------------------------------------------------------------------------------- 1 | -module(typed_record_field_access). 2 | 3 | -record(r, {a, b}). 4 | 5 | -type r() :: #r{a :: integer()}. 6 | 7 | -spec b(r()) -> any(). 8 | b(R) -> 9 | R#r.b. 10 | -------------------------------------------------------------------------------- /test/should_pass/unary_negate_union_with_user_type_pass.erl: -------------------------------------------------------------------------------- 1 | -module(unary_negate_union_with_user_type_pass). 2 | 3 | -type price() :: integer(). 4 | 5 | -spec price(_) -> float() | price(). 6 | price(_) -> 100. 7 | 8 | -spec f(_, _) -> boolean(). 9 | f(G, ReversePaymentG) -> 10 | price(G) =:= - price(ReversePaymentG). 11 | -------------------------------------------------------------------------------- /test/should_pass/unary_plus.erl: -------------------------------------------------------------------------------- 1 | -module(unary_plus). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec m(+1) -> {}. 6 | m(+1) -> 7 | {}. 8 | 9 | n(+1) -> 10 | {}. 11 | 12 | -spec o() -> integer(). 13 | o() -> 14 | +5. 15 | 16 | 17 | p() -> 18 | +5. 19 | -------------------------------------------------------------------------------- /test/should_pass/underscore.erl: -------------------------------------------------------------------------------- 1 | -module(underscore). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec underscore(_) -> _. 6 | underscore(1) -> 7 | "apa". 8 | -------------------------------------------------------------------------------- /test/should_pass/user_type_in_pattern_body.erl: -------------------------------------------------------------------------------- 1 | -module(user_type_in_pattern_body). 2 | 3 | %% Check that the pattern type is normalized when recursively checking a pattern 4 | 5 | -compile([export_all, nowarn_export_all]). 6 | 7 | -type t() :: {}. 8 | 9 | %% Cons: The element type t() should be normalized to {}, to check that the head 10 | %% pattern {} has type t(). 11 | -spec cons([t()]) -> boolean(). 12 | cons([{} | _]) -> true; 13 | cons([]) -> false. 14 | 15 | %% Map: Key {} and value {} are of type t() 16 | -spec map(#{t() => t()}) -> boolean(). 17 | map(#{{} := {}}) -> true; 18 | map(#{}) -> false. 19 | 20 | %% record field type normalized in patterns 21 | -record(r, {a :: t()}). 22 | -spec rec(#r{}) -> ok. 23 | rec(#r{a = {}}) -> ok. 24 | -------------------------------------------------------------------------------- /test/should_pass/user_types.erl: -------------------------------------------------------------------------------- 1 | -module(user_types). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% these exported types are used in remote_types module 6 | -export_type([my_tuple/0, my_list/0, my_union/0, my_opaque/0, 7 | my_empty_record/0, my_refined_record/0, my_generic/1]). 8 | 9 | -record(r, {}). 10 | -type my_empty_record() :: #r{}. 11 | -record(refined_r, {a, b}). 12 | -type my_refined_record() :: #refined_r{a :: integer(), b :: binary()}. 13 | -type my_atom() :: atom(). 14 | -type my_tuple() :: {my_atom(), integer()}. 15 | -type my_list() :: list(my_atom()). 16 | -type my_union() :: float() | my_tuple(). 17 | 18 | %% v-- Either a local type (my_tuple) to this module or a remote user type 19 | -type my_generic(RetType) :: my_shadowed_type() | {ok, RetType}. 20 | %% A type to be shadowed by a type with the same name in a local module. 21 | %% i.e. `user_types:my_generic(my_shadowed_type())` where `my_shadowed_type()` 22 | %% is defined in the module using the remoty type `my_generic` above. 23 | -type my_shadowed_type() :: user_types. 24 | 25 | -opaque my_opaque() :: integer(). 26 | 27 | -spec f(my_tuple()) -> any(). 28 | f({A, _}) -> 29 | A. 30 | 31 | -spec new_opaque() -> my_opaque(). 32 | new_opaque() -> 33 | 0. 34 | 35 | -spec update_opaque(my_opaque()) -> my_opaque(). 36 | update_opaque(N) -> 37 | N + 1. 38 | -------------------------------------------------------------------------------- /test/should_pass/var.erl: -------------------------------------------------------------------------------- 1 | -module(var). 2 | 3 | -export([id/1]). 4 | 5 | -spec id(A) -> A. 6 | id(A) -> A. 7 | -------------------------------------------------------------------------------- /test/should_pass/var_fun.erl: -------------------------------------------------------------------------------- 1 | -module(var_fun). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | f() -> 6 | lists:map(fun (F) -> F(5) end, [ fun ?MODULE:F/1 || F <- [apa, bepa]]). 7 | 8 | -spec g() -> fun((integer()) -> integer()). 9 | g() -> 10 | F = apa, 11 | fun ?MODULE:F/1. 12 | 13 | h() -> 14 | M = ?MODULE, 15 | F = apa, 16 | A = 1, 17 | fun M:F/A. 18 | 19 | apa(X) -> 20 | X * 2. 21 | 22 | bepa(X) -> 23 | X + 2. 24 | -------------------------------------------------------------------------------- /test/should_pass/varbind_in_block.erl: -------------------------------------------------------------------------------- 1 | -module(varbind_in_block). 2 | 3 | -export([add_vars/2]). 4 | 5 | -spec add_vars(1..2, 2..3) -> 2. 6 | add_vars(A, B) -> 7 | V = A, 8 | V = B, 9 | V. 10 | -------------------------------------------------------------------------------- /test/should_pass/varbind_in_case.erl: -------------------------------------------------------------------------------- 1 | -module(varbind_in_case). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | %% Don't forget variable bindings in case scrutinee. 6 | 7 | %% Inference mode 8 | -spec foo() -> ok. 9 | foo() -> 10 | case Ok = ok of 11 | ok -> ok 12 | end, 13 | Ok. 14 | 15 | %% Checking mode 16 | -spec ok(ok) -> ok. 17 | ok(ok) -> ok. 18 | 19 | -spec bar() -> ok. 20 | bar() -> 21 | ok(case Ok = ok of ok -> ok end), 22 | Ok. 23 | 24 | -spec merge_vars(boolean(), 1..2, 2..3) -> 1..3. 25 | merge_vars(A, B, C) -> 26 | case A of 27 | true -> V = B; 28 | false -> V = C 29 | end, 30 | V. 31 | 32 | -spec tuple_bind({foo, integer()} | {bar, float()}) -> number(). 33 | tuple_bind(A) -> 34 | case A of 35 | {foo, N} -> 36 | ok; 37 | {bar, N} -> 38 | ok 39 | end, 40 | N. 41 | -------------------------------------------------------------------------------- /test/should_pass/varbind_in_function_head.erl: -------------------------------------------------------------------------------- 1 | -module(varbind_in_function_head). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | -compile([nowarn_shadow_vars, nowarn_unused_vars]). 5 | 6 | a({Key, Value, undefined} = _Unused) -> 7 | {Key, Value, 0}; 8 | a({Key, Value, Timestamp}) -> 9 | {Key, Value, 2}. 10 | 11 | -record(b, { 12 | field1 :: atom() 13 | }). 14 | b(#b{field1 = undefined} = _Unused) -> 15 | 1; 16 | b(#b{field1 = _}) -> 17 | 2. 18 | -------------------------------------------------------------------------------- /test/should_pass/varbind_in_lc.erl: -------------------------------------------------------------------------------- 1 | -module(varbind_in_lc). 2 | 3 | -compile([export_all, nowarn_export_all]). 4 | 5 | -spec is_true(true) -> true. 6 | is_true(true) -> true. 7 | 8 | %% Don't forget variable bindings in lc guard. 9 | 10 | lc1() -> 11 | [ X || X = true, X == true ]. 12 | 13 | lc2() -> 14 | [ X || X = true, is_true(X) ]. 15 | 16 | bc1() -> 17 | << <<0>> || X = true, X == true >>. 18 | 19 | bc2() -> 20 | << <<0>> || X = true, is_true(X) >>. 21 | 22 | -------------------------------------------------------------------------------- /test/should_pass/variable_binding.erl: -------------------------------------------------------------------------------- 1 | -module(variable_binding). 2 | 3 | -export([variable_bind_before_record_pattern_match/2]). 4 | 5 | -record(st, {}). 6 | 7 | variable_bind_before_record_pattern_match({Pid}, #st{}) -> 8 | Pid. 9 | -------------------------------------------------------------------------------- /test/should_pass/variable_binding_leaks.erl: -------------------------------------------------------------------------------- 1 | -module(variable_binding_leaks). 2 | 3 | -export([case_clauses/1]). 4 | 5 | -spec case_clauses(foo | integer()) -> integer(). 6 | case_clauses(X) -> 7 | case X of 8 | Y = foo -> length(atom_to_list(Y)); 9 | Y -> Y + 1 % a different Y 10 | end. 11 | -------------------------------------------------------------------------------- /test/test_lib.erl: -------------------------------------------------------------------------------- 1 | -module(test_lib). 2 | 3 | -export([create_env_from_file/2, 4 | create_env/1, create_env/2]). 5 | 6 | create_env_from_file(FileName, Opts) -> 7 | {ok, Data} = file:read_file(FileName), 8 | gradualizer:env(binary_to_list(Data), Opts). 9 | 10 | create_env(Opts) -> 11 | gradualizer:env(Opts). 12 | 13 | create_env(TypeEnvString, Opts) -> 14 | gradualizer:env(TypeEnvString, Opts). 15 | -------------------------------------------------------------------------------- /test/undefined_errors_test.erl: -------------------------------------------------------------------------------- 1 | -module(undefined_errors_test). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | undefined_errors_test_() -> 6 | {setup, 7 | fun () -> 8 | {ok, Apps} = application:ensure_all_started(gradualizer), 9 | gradualizer_db:import_erl_files( 10 | ["priv/test/undefined_errors_helper.erl"]), 11 | Apps 12 | end, 13 | fun (Apps) -> 14 | [ok = application:stop(App) || App <- Apps], 15 | ok 16 | end, 17 | [?_test(check_file("test/misc/undefined_errors.erl")), 18 | ?_test(check_file("test/misc/lint_errors.erl"))]}. 19 | 20 | check_file(File) -> 21 | Errors = gradualizer:type_check_file(File, [return_errors]), 22 | %% Test that error formatting doesn't crash 23 | Opts = [{fmt_location, brief}, 24 | {fmt_expr_fun, fun erl_prettypr:format/1}], 25 | lists:foreach(fun({_, Error}) -> gradualizer_fmt:handle_type_error(Error, Opts) end, Errors), 26 | {ok, Forms} = gradualizer_file_utils:get_forms_from_erl(File, []), 27 | ExpectedErrors = typechecker:number_of_exported_functions(Forms), 28 | ?assertEqual(ExpectedErrors, length(Errors)). 29 | --------------------------------------------------------------------------------